C語言經常讓人以爲它所能表達的東西很是有限。它不具備相似第一級函數和模式匹配這樣的高級功能。可是C很是簡單,而且仍然有一些很是有用的語法技巧和功能,只是沒有多少人知道罷了。linux
指定的初始化
不少人都知道像這樣來靜態地初始化數組:express
intfibs[] = {1, 1, 2, 3, 5};
C99標準實際上支持一種更爲直觀簡單的方式來初始化各類不一樣的集合類數據(如:結構體,聯合體和數組)。編程
數組
咱們能夠指定數組的元素來進行初始化。這很是有用,特別是當咱們須要根據一組#define來保持某種映射關係的同步更新時。來看看一組錯誤碼的定義,如:數組
/* Entries may not correspond to actual numbers. Some entries omitted. */
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/* ... */
#define E2BIG 7
#define EBUSY 8
/* ... */
#define ECHILD 12
/* ... */
如今,假設咱們想爲每一個錯誤碼提供一個錯誤描述的字符串。爲了確保數組保持了最新的定義,不管頭文件作了任何修改或增補,咱們均可以用這個數組指定的語法。函數
char*err_strings[] = {
[0] ="Success",
[EINVAL] ="Invalid argument",
[ENOMEM] ="Not enough memory",
[EFAULT] ="Bad address",
/* ... */
[E2BIG ] ="Argument list too long",
[EBUSY ] ="Device or resource busy",
/* ... */
[ECHILD] ="No child processes"
/* ... */
};
這樣就能夠靜態分配足夠的空間,且保證最大的索引是合法的,同時將特殊的索引初始化爲指定的值,並將剩下的索引初始化爲0。
oop
結構體與聯合體
用結構體與聯合體的字段名稱來初始化數據是很是有用的。假設咱們定義:ui
structpoint {
intx;
inty;
intz;
}
而後咱們這樣初始化structpoint:
1
structpoint p = {.x = 3, .y = 4, .z = 5};
當咱們不想將全部字段都初始化爲0時,這種做法能夠很容易的在編譯時就生成結構體,而不須要專門調用一個初始化函數。 www.2cto.comspa
對聯合體來講,咱們可使用相同的辦法,只是咱們只用初始化一個字段。索引
宏列表
C中的一個慣用方法,是說有一個已命名的實體列表,須要爲它們中的每個創建函數,將它們中的每個初始化,並在不一樣的代碼模塊中擴展它們的名字。這在Mozilla的源碼中常常用到,我就是在那時學到這個技巧的。例如,在我去年夏天工做的那個項目中,咱們有一個針對每一個命令進行標記的宏列表。其工做方式以下:rem
#define FLAG_LIST(_) \
_(InWorklist) \
_(EmittedAtUses) \
_(LoopInvariant) \
_(Commutative) \
_(Movable) \
_(Lowered) \
_(Guard)
它定義了一個FLAG_LIST宏,這個宏有一個參數稱之爲 _ ,這個參數自己是一個宏,它可以調用列表中的每一個參數。舉一個實際使用的例子可能更能直觀地說明問題。假設咱們定義了一個宏DEFINE_FLAG,如:
#define DEFINE_FLAG(flag) flag,
enumFlag {
None = 0,
FLAG_LIST(DEFINE_FLAG)
Total
};
#undef DEFINE_FLAG
對FLAG_LIST(DEFINE_FLAG)作擴展可以獲得以下代碼:
enumFlag {
None = 0,
DEFINE_FLAG(InWorklist)
DEFINE_FLAG(EmittedAtUses)
DEFINE_FLAG(LoopInvariant)
DEFINE_FLAG(Commutative)
DEFINE_FLAG(Movable)
DEFINE_FLAG(Lowered)
DEFINE_FLAG(Guard)
Total
};
接着,對每一個參數都擴展DEFINE_FLAG宏,這樣咱們就獲得了enum以下:
enumFlag {
None = 0,
InWorklist,
EmittedAtUses,
LoopInvariant,
Commutative,
Movable,
Lowered,
Guard,
Total
};
接着,咱們可能要定義一些訪問函數,這樣才能更好的使用flag列表:
#define FLAG_ACCESSOR(flag) \
boolis##flag()const{\
returnhasFlags(1 << flag);\
}\
voidset##flag() {\
JS_ASSERT(!hasFlags(1 << flag));\
setFlags(1 << flag);\
}\
voidsetNot##flag() {\
JS_ASSERT(hasFlags(1 << flag));\
removeFlags(1 << flag);\
}
FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR
一步步的展現其過程是很是有啓發性的,若是對它的使用還有不解,能夠花一些時間在gcc –E上。
編譯時斷言
這實際上是使用C語言的宏來實現的很是有「創意」的一個功能。有些時候,特別是在進行內核編程時,在編譯時就可以進行條件檢查的斷言,而不是在運行時進行,這很是有用。不幸的是,C99標準還不支持任何編譯時的斷言。
可是,咱們能夠利用預處理來生成代碼,這些代碼只有在某些條件成立時纔會經過編譯(最好是那種不作實際功能的命令)。有各類各樣不一樣的方式均可以作到這一點,一般都是創建一個大小爲負的數組或結構體。最經常使用的方式以下:
/* Force a compilation error if condition is false, but also produce a result
* (of value 0 and type size_t), so it can be used e.g. in a structure
* initializer (or wherever else comma expressions aren't permitted). */
/* Linux calls these BUILD_BUG_ON_ZERO/_NULL, which is rather misleading. */
#define STATIC_ZERO_ASSERT(condition) (sizeof(struct { int:-!(condition); }) )
#define STATIC_NULL_ASSERT(condition) ((void *)STATIC_ZERO_ASSERT(condition) )
/* Force a compilation error if condition is false */
#define STATIC_ASSERT(condition) ((void)STATIC_ZERO_ASSERT(condition))
若是(condition)計算結果爲一個非零值(即C中的真值),即! (condition)爲零值,那麼代碼將能順利地編譯,並生成一個大小爲零的結構體。若是(condition)結果爲0(在C真爲假),那麼在試圖生成一個負大小的結構體時,就會產生編譯錯誤。
它的使用很是簡單,若是任何某假設條件可以靜態地檢查,那麼它就能夠在編譯時斷言。例如,在上面提到的標誌列表中,標誌集合的類型爲uint32_t,因此,咱們能夠作如下斷言:
STATIC_ASSERT(Total <= 32)
它擴展爲:
(void)sizeof(struct{int:-!(Total <= 32) })
如今,假設Total<=32。那麼-!(Total <= 32)等於0,因此這行代碼至關於:
(void)sizeof(struct{int: 0 })
這是一個合法的C代碼。如今假設標誌不止32個,那麼-!(Total <= 32)等於-1,因此這時代碼就至關於:
(void)sizeof(struct{int: -1 } )
由於位寬爲負,因此能夠肯定,若是標誌的數量超過了咱們指派的空間,那麼編譯將會失敗。