http://blog.jobbole.com/16035/
http://www.endofunctor.org/~rpearl/blog/cool-c-tricks.html
編譯時斷言
這其實是使用C語言的宏來實現的非常有“創意”的一個功能。有些時候,特別是在進行內核編程時,在編譯時就能夠進行條件檢查的斷言,而不是在運行時進行,這非常有用。不幸的是,C99標準還不支持任何編譯時的斷言。
但是,我們可以利用預處理來生成代碼,這些代碼只有在某些條件成立時纔會通過編譯(最好是那種不做實際功能的命令)。有各種各樣不同的方式都可以做到這一點,通常都是建立一個大小爲負的數組或結構體。最常用的方式如下:
1
2
3
4
5
6
7
8
9
|
/*
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,所以,我們可以做以下斷言:
1
|
STATIC_ASSERT(Total
<= 32) |
它擴展爲:
1
|
( void ) sizeof ( struct
{ int :-!(Total
<= 32) }) |
現在,假設Total<=32。那麼-!(Total <= 32)
等於0,所以這行代碼相當於:
1
|
( void ) sizeof ( struct
{ int :
0 }) |
這是一個合法的C代碼。現在假設標誌不止32個,那麼-!(Total <= 32)
等於-1,所以這時代碼就相當於:
1
|
( void ) sizeof ( struct
{ int :
-1 } ) |
因爲位寬爲負,所以可以確定,如果標誌的數量超過了我們指派的空間,那麼編譯將會失敗。
指定的初始化
很多人都知道像這樣來靜態地初始化數組:
1
|
int
fibs[] = {1, 1, 2, 3, 5}; |
C99標準實際上支持一種更爲直觀簡單的方式來初始化各種不同的集合類數據(如:結構體,聯合體和數組)。
數組
我們可以指定數組的元素來進行初始化。這非常有用,特別是當我們需要根據一組#define來保持某種映射關係的同步更新時。來看看一組錯誤碼的定義,如:
1
2
3
4
5
6
7
8
9
10
|
/*
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 /*
... */ |
現在,假設我們想爲每個錯誤碼提供一個錯誤描述的字符串。爲了確保數組保持了最新的定義,無論頭文件做了任何修改或增補,我們都可以用這個數組指定的語法。
1
2
3
4
5
6
7
8
9
10
11
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。
結構體與聯合體
1
|
用結構體與聯合體的字段名稱來初始化數據是非常有用的。假設我們定義: |
1
2
3
4
5
|
struct
point { int
x; int
y; int
z; } |
1
|
然後我們這樣初始化 struct
point: |
1
|
struct
point p = {.x = 3, .y = 4, .z = 5}; |
當我們不想將所有字段都初始化爲0時,這種作法可以很容易的在編譯時就生成結構體,而不需要專門調用一個初始化函數。
對聯合體來說,我們可以使用相同的辦法,只是我們只用初始化一個字段。
宏列表
C中的一個慣用方法,是說有一個已命名的實體列表,需要爲它們中的每一個建立函數,將它們中的每一個初始化,並在不同的代碼模塊中擴展它們的名字。這在Mozilla的源碼中經常用到,我就是在那時學到這個技巧的。例如,在我去年夏天工作的那個項目中,我們有一個針對每個命令進行標記的宏列表。其工作方式如下:
1
2
3
4
5
6
7
8
|
#define
FLAG_LIST(_) \ _(InWorklist)
\ _(EmittedAtUses)
\ _(LoopInvariant)
\ _(Commutative)
\ _(Movable)
\ _(Lowered)
\ _(Guard) |
它定義了一個FLAG_LIST宏,這個宏有一個參數稱之爲 _ ,這個參數本身是一個宏,它能夠調用列表中的每個參數。舉一個實際使用的例子可能更能直觀地說明問題。假設我們定義了一個宏DEFINE_FLAG,如:
1
2
3
4
5
6
7
|
#define
DEFINE_FLAG(flag) flag, enum
Flag { None
= 0, FLAG_LIST(DEFINE_FLAG) Total }; #undef
DEFINE_FLAG |
對FLAG_LIST(DEFINE_FLAG)做擴展能夠得到如下代碼:
1
2
3
4
5
6
7
8
9
10
11
|
enum
Flag { 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如下:
1
2
3
4
5
6
7
8
9
10
11
|
enum
Flag { None
= 0, InWorklist, EmittedAtUses, LoopInvariant, Commutative, Movable, Lowered, Guard, Total }; |
接着,我們可能要定義一些訪問函數,這樣才能更好的使用flag列表:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#define
FLAG_ACCESSOR(flag) \ bool
is##flag() const
{\ return
hasFlags(1 << flag);\ }\ void
set##flag() {\ JS_ASSERT(!hasFlags(1
<< flag));\ setFlags(1
<< flag);\ }\ void
setNot##flag() {\ JS_ASSERT(hasFlags(1
<< flag));\ removeFlags(1
<< flag);\ } FLAG_LIST(FLAG_ACCESSOR) #undef
FLAG_ACCESSOR |
一步步的展示其過程是非常有啓發性的,如果對它的使用還有不解,可以花一些時間在gcc –E上。