c語言是一門古老的語言,可以看下下面的C語言的介紹:
1969-1973年在美國電話電報公司(AT&T)貝爾實驗室開始了C語言的最初研發。根據C語言的發明者丹尼斯·裏奇 (Dennis Ritchie) 說,C 語言最重要的研發時期是在1972年。
說明:丹尼斯·裏奇(Dennis Ritchie),C語言之父,UNIX之父。1978年與布萊恩·科爾尼幹(Brian Kernighan)一起出版了名著《C程序設計語言(The C Programming Language)》,現在此書已翻譯成多種語言,成爲C語言方面最權威的教材之一。2011年10月12日(北京時間爲10月13日),丹尼斯·裏奇去世,享年70歲。
C語言之所以命名爲C,是因爲C語言源自Ken Thompson發明的 B語言,而B語言則源自BCPL語言。
C語言的誕生是和UNIX操作系統的開發密不可分的,原先的UNIX操作系統都是用匯編語言寫的,1973年UNIX操作系統的核心用C語言改寫,從此以後,C語言成爲編寫操作系統的主要語言。
C語言既簡單又複雜,說它簡單是因爲它的關鍵字少,語法規則簡單,看看就可以編寫個hello,world程序;說它複雜是因爲它接近於底層,有指針,可以直接操作內存,由此引起的一堆麻煩事情調試起來有非常的複雜,而且沒有豐富的庫的支持,很多東西都要自己手寫,比較麻煩。
C語言的另外複雜點在於,你也許對C的語法早就瞭然於胸,但是仍然對開源的庫代碼閱讀起來非常喫力,除了算法和數據結構複雜之外,C語言還有自己的奇技淫巧,常在開源的代碼中應用,但是卻很少有書去總結,這個文章算是對C的常用技巧做一個總結吧,資料來源於網上和自己看代碼的一些體會。
一 編譯器判斷優化技巧
#define likely(x) __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)
likely這個宏的期望x是非0,是在絕大多數情況下,x都是非0,比如我們在內存申請後判斷指針是否爲空可以用,而unlikely正好相反,是絕大多數情況下,x爲空時候使用。
char * p =(char*)malloc(sizeof(int));
if (likely(p)) {
do_something();
}
引入這兩個宏,可以增加條件判斷的分支預測準確性,cpu會提前裝載後面的指令。在彙編級別的表現是預測大多數可能發生的條件是順序的指令,而少數可能發生的情況是跳轉指令,順序指令在執行的時候可以利用CPU的緩存優勢。
極端情況下,性能可以提升30%左右。
二 定長類型
在Java這種語言中,byte是8個位一個字節,short是16位,2個字節,int是32位,4個字節這些都是確定的。C語言中,經常出現同一個類型在不同的平臺的字節長度是不一樣的,比如long在32位系統中爲4個字節,在64位系統中爲8個字節。這就給我們編寫跨平臺的系統產生了麻煩,
爲了跨平臺,很多系統定義了自己的一套類型。stdint.h頭文件到了確定大小的類型,比如:
1字節 uint8_t
2字節 uint16_t
4字節 uint32_t
8字節 uint64_t
編程中,我們應該多使用這些類型,少使用int,long等。
三 利用宏實現日誌功能
日誌功能很常用,但是我們如何獲取打印日誌的位置和行號那,我特意和同事討論了下,在Java中這個功能是通過定義異常來實現的,那麼在C中如何實現,廢話不說,看下代碼吧:
#define LOG_PRINTF(pres, fmt, ...) \
log_printf(pres "[%s:%s:%d]" fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#define log_info(logLevel,fmt,...) do { \
if (INFO_LEVEL >= logLevel) {\
LOG_PRINTF_S("[INFO]", fmt, ##__VA_ARGS__);\
}\
}while (0);
void log_printf(const char * fmt, ...) {
va_list ap;
// 每條日誌大小, 按照系統緩衝區走
char str[BUFSIZ];
int len = times_fmt("["STR_TIMES"]", str, sizeof str);
time_t now_time = time(NULL);
//每到新的一天切換日誌
if (!time_day(now_time,last_time)) {
log_init(g_app_conf.app_conf.common_conf_data.log_file_name);
}
// 日誌內容填充
va_start(ap, fmt);
vsnprintf(str + len, sizeof str - len, fmt, ap);
va_end(ap);
// 數據寫入到文件中
fputs(str, log_file);
if (is_write_to_console == 1) {
fputs(str, stderr);
}
}
說明:
1) FILE表示正在運行程序文件名,func正在執行的函數,LINE是程序文件中的行號,這些是C的編譯器內置的宏。
2) 對於#宏解釋下,#用在預編譯語句裏面可以把預編譯函數的變量直接格式成字符串。
比如:
#define my_printf(x) printf(#x" is %d\n", x)
int a = 100;
my_printf(a);
/*打印a is 100*/
可以用在switch語句中,將enum轉成相關的字符串,非常方便:
#define CASE_CODE(E) case E: return #E
const char * PacketProfileDetectIdToString(PacketProfileDetectId id)
{
switch (id) {
CASE_CODE (PROF_DETECT_SETUP);
CASE_CODE (PROF_DETECT_GETSGH);
CASE_CODE (PROF_DETECT_IPONLY);
default:
return "UNKNOWN";
}
}
3) 對於##宏解釋:
/* ## 是變量連接符,將兩個字符連接成一個變量 */
#define FUN(a) printf("The square of " #a " is %d.\n",b##a)
int bm = 2
FUN(m)
/* 打印 The square of m is 2.*/
4) VA_ARGS是可變參數宏,表示可變參數列表。
#define Debug(...) printf(__VA_ARGS__)
Debug("Y = %d\n", y);
/*自動替換成:
printf("Y = %d\n", y);
*/
5) 對於 ##__VA_ARGS__
宏 作用是如果最後的可變變量爲空忽略後面的逗號。
6) vsnprintf 是按照特定格式將可變參數打印到字符串中,方便後面的輸出。
順便說一下,宏在C語言中真是離不開,雖然很多書都推薦不要用宏,因爲不便調試,但是宏可以簡化代碼,提高效率,使用範圍是相當的廣。
四 變長結構體
在C語言中,本身是不支持動態數組的,但是有些技巧可以實現類似動態數組的效果,一般人可能這樣定義:
typedef struct Arry {
int arry_len;
char * content;
} * Arry;
//申請內存
Arry*p_ptr = (Arry*)malloc(sizeof(parry));
p_ptr ->content = (char *)malloc(100);
//釋放內存
free(p_ptr ->content);
free(p_ptr);
這裏面我們注意到這個這個結構體有兩個變量,一個是int,一個是char ,char 裏面保存的是申請內存的指針,如果用sizeof去求的話,會發現整個結構體的大小爲4+4 = 8。
p_ptr 指針和p_ptr ->content 指針指向的內存沒有任何關係。
變長結構體定義如下:
typedef struct
{
int a;
char b[0];
} * DArray;
//申請內存 100
DArray p_darray = (DArray)malloc(sizeof(*DArray)+100);
//釋放內存
free(p_darray);
相比上面的定義好處如下:
1)只需要申請和釋放內存一次即可。
2)內存分配是連續的,可以減少內存碎片。
3)節省內存,sizeof(*DArray) = = 4.
4) 可以方便用來做socket數據包傳輸,解析數據,等。
五 求數組和枚舉的小技巧
對於一些情況,我們需要用到數組成員的個數和枚舉的大小,一般對數組求大小可以這樣做:sizeof(array)/sizeof(array[0])
得到;對於枚舉類型,我們可以在最後定義一個最大宏,標識宏的結束:
enum { ONE,TWO,THREE,MAX};
我們在循環的時候就可以用MAX,以後添加變量在MAX前面添加即可,相關代碼不用改變。
六 宏定義的小技巧
#define EXAMPLE do{
xxxx \
xxxxx \
}while(0);
//這樣好處把宏定義封裝起來,後面多加分號不容易出錯。
七 NULL 判斷顛倒處理
我們判斷NULL指針的時候,一般用if (p == NULL)
,但是這種寫法,有可能少寫一個=號,而程序不報錯,可以改成 if (NULL == p)
這樣寫之後如果少寫一個=號,程序顯然會報錯的。
八 其他提示
C語言的告警一定要多注意,儘量讓代碼零告警,不僅僅看起來清爽,還可以避免不少難查的Bug,還有一點就是程序寫好之後,用valgrind --tool=memcheck --leak-check=full
運行檢查,
看看是否有內存泄漏,還有就是invaild 讀和寫,這往往是程序運行core的根源,切記切記!