**一、宏列表**
當遇到這樣的問題的時候:
有一個標記變量,其中的每個位代表相應的含義。我們需要提供一組函數來訪問設置這些位,但是對于每個標記位的操作函數都是相似的。若有32個位,難道要搞32套相似的操作函數么?
你也許會說,用一套操作函數,根據傳入的參數來判斷對哪個位操作。這樣固然可行,但是
①不夠直觀。例如訪問Movable標記位,對于用戶來說,is Movable()是很自然的方式,而我們只能提供這樣的接口isFlag(Movable)
②擴展性差。若以后增加刪改標記位,則需要更改isFlag等函數的代碼。
我們想有這樣的設計:
在頭文件的宏定義中增刪標記位的宏,我們為每個標記位設計的操作函數名就自動更改,增加的標記位也自動增加一套操作函數,刪除的標記位也自動減去一套操作函數。
這樣的設計就太爽了!
**但如何實現呢?**
首先,每個標記位的宏名一變,我們的操作函數名也要相應改變,這時我們可以想到用帶參宏,并用宏的##符,把兩個字符串合在一起。(使它們能被宏替換掉)
**#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_ACCESSOR(Movable)就可得到操作Movable標記位的三個函數:is Movable(),set Movable(),setNot Movable()
但是,難道有多少個標記位,我們就要寫多少個FLAG_ACCESSOR(flag)么?
如何用一個式子來擴展成多個種的FLAG_ACCESSOR(flag),**提取共性**,由于這多個FLAG_ACCESSOR(flag),flag是不同的,宏函數名是相同的。故用宏列表:
**#define FLAG_LIST(_) ? ? ? ?\**
**??_(InWorklist)?????????????????????\**
**??_(EmittedAtUses) ? ? ? ? ? ?\**
**??_(LoopInvariant) ? ? ? ? ? ? ?\**
**??_(Commutative) ? ? ? ? ? ? ?\**
**??_(Movable)?????????????????????? \**
**??_(Lowered) ? ? ? ? ? ? ? ? ? ? ?\**
**??_(Guard)**
這樣一個式子:FLAG_LIST(FLAG_ACCESSOR)就搞定了。
但是,還有一個問題,我們還沒有定義InWorklist、EmittedAtUses、LoopInvariant等,需要再用宏來定義這些標記位的名字。
例如:
**#define InWorklist 1**
**#define EmittedAtUses 2**
**……**
這樣以來,若以后我們增改標記位的名字 就需要修改兩處地方了:宏列表、標記名的宏定義。
?
**我們想要的最好的設計是,只改變一處 處處跟著一起改變。**
[yang]若是有新的標記位加入我們只在#define FLAG_LIST(_) 中添加一項就好了。例如,_(Visited) 自動添加#define Visited 8。
自動添加一項宏定義難以實現,那我們考慮有沒有替代方案,觀察發現此宏定義都是定義的數字,而枚舉也有同樣的功能。
這樣,我們把這些展開的位標記名放在enum枚舉中,讓其自動賦上1,2,3……等數值,而不必用宏定義一個一個地定義。
現在問題變為:如何使我們在#defineFLAG_LIST(_) 中添加一項,enum枚舉中就自動添加相應的一項?
我們只有把FLAG_LIST(_)放入enum枚舉中,這樣才能一增俱增。
若宏列表:
**#define FLAG_LIST(_) ? ? ? ?\**
**??_(InWorklist)?????????????????????\**
**??_(EmittedAtUses) ? ? ? ? ? \**
**_(LoopInvariant)??????????????????**
**能再變為:**
**InWorklist**
**EmittedAtUses**
**LoopInvariant**
就好了。
這樣,我們在#defineFLAG_LIST(_) 中添加一項_(Visited)。則enum中自動添加Visited。
也就是_(InWorklist)如何展開成InWorklist。這個很簡單:#define DEFINE_FLAG(flag)flag,
**其具體實現方式如下:**
**#define FLAG_LIST(_) ? ? ? \**
**??_(InWorklist)?????????????????????\**
**??_(EmittedAtUses) ? ? ? ? ? \**
**??_(LoopInvariant) ? ? ? ? ? ? \**
**??_(Commutative) ? ? ? ? ? ? \**
**??_(Movable) ? ? ? ? ? ? ? ? ? ? ?\**
**??_(Lowered) ? ? ? ? ? ? ? ? ? ? \**
**??_(Guard)**
它定義了一個FLAG_LIST宏,這個宏有一個參數稱之為 _ ,這個參數本身是一個宏,它能夠調用列表中的每個參數。舉一個實際使用的例子可能更能直觀地說明問題。假設我們定義了一個宏DEFINE_FLAG,如:
**#define DEFINE_FLAG(flag) flag,?? //注意flag后有逗號**
**?enum Flag {**
**?????None = 0,**
**?????FLAG_LIST(DEFINE_FLAG)**
**?????Total**
**? };**
**#undef DEFINE_FLAG**
對FLAG_LIST(DEFINE_FLAG)做擴展能夠得到如下代碼:
**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如下:
**enum Flag {**
**??????None = 0,**
**??????InWorklist,**
**??????EmittedAtUses,**
**??????LoopInvariant,**
**??????Commutative,**
**??????Movable,**
**??????Lowered,**
**??????Guard,**
**??????Total**
**??};**
接著,我們可能要定義一些訪問函數,這樣才能更好的使用flag列表:
**#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**
(這樣,我們只在宏列表一處更改增刪位操作即可。)
【總結:yang】

一步步的展示其過程是非常有啟發性的,如果對它的使用還有不解,可以花一些時間在gcc –E上。
【宏列表的優點有:可以把一個式子擴展成多個式子,且很容易擴展,只要再增加列表項即可。】
二、**指定的初始化**
很多人都知道像這樣來靜態地初始化數組:
int fibs[] = {1,2,3,4,5} ;
C99標準實際上支持一種更為直觀簡單的方式來初始化各種不同的集合類數據(如:結構體,聯合體和數組)。
**數組的初始化**
我們可以**指定數組的元素來進行初始化**。這非常有用,**特別是當我們需要根據一組#define來保持某種映射關系的同步更新時**。來看看一組錯誤碼的定義,如:
/* Entries may not correspond to actualnumbers. Some entries omitted. */
#define EINVAL 1
#define ENOMEM 2
#define EFAULT 3
/* ... */
#define E2BIG? 7
#define EBUSY? 8
/* ... */
#define ECHILD 12
/* ... */
現在,假設我們想為每個錯誤碼提供一個錯誤描述的字符串。為了確保數組保持了最新的定義,無論頭文件做了任何修改或增補,我們都可以用這個數組指定的語法。
char *err_strings[] = {
?err_strings[0] = "Success",
?err_strings?[EINVAL] = "Invalid argument",
?err_strings[ENOMEM] = "Not enough memory",
?err_strings[EFAULT] = "Bad address",
??/* ... */
?err_strings[E2BIG ] = "Argument list too long",
?err_strings[EBUSY ] = "Device or resource busy",
??/* ... */
err_strings[ECHILD] = "No child processes"
??/* ... */
};
**這樣就可以靜態分配足夠的空間,且保證最大的索引是合法的,同時將特殊的索引初始化為指定的值,并將剩下的索引初始化為0。**
(注意:指定元素前面要有數組名,否則報錯)
**結構體與聯合體**
**用結構體與聯合體的字段名稱來初始化數據**是非常有用的。假設我們定義:
struct point {
??int x;
??int y;
??int z;
} ;
然后我們這樣初始化structpoint:
struct point p = {.x = 1, ?.z = 3};?//x為1,y為0,z為3
當我們不想將所有字段都初始化為0時,這種作法可以很容易的在編譯時就生成結構體,而不需要專門調用一個初始化函數。
對聯合體來說,我們可以使用相同的辦法,只是我們只用初始化一個字段。
**三、編譯時斷言**
這其實是使用C語言的宏來實現的非常有“創意”的一個功能。有些時候,特別是在進行內核編程時,在編譯時就能夠進行條件檢查的斷言,而不是在運行時進行,這非常有用。不幸的是,C99標準還不支持任何編譯時的斷言。
但是,我們可以利用預處理來生成代碼,這些代碼只有在某些條件成立時才會通過編譯(最好是那種不做實際功能的命令)。**有各種各樣不同的方式都可以做到這一點,通常都是建立一個大小為負的數組或結構體**。最常用的方式如下:
#define STATIC_ZERO_ASSERT(condition)(sizeof(struct { int:-!(condition); })???)
#define STATIC_NULL_ASSERT(condition)((void *)STATIC_ZERO_ASSERT(condition)???)?
// 上面是用兩種不同的方式來實現這個效果的,我們任選其中一種方式即可?
【原理】
#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 } )
因為位寬為負,所以可以確定,如果標志的數量超過了我們指派的空間,那么編譯將會失敗。
**四、在指針中隱藏數據**
(這個技術有點變態,大家看看就好)
編寫 C 語言代碼時,指針無處不在。我們可以稍微額外利用指針,在它們內部暗中存儲一些額外信息。為實現這一技巧,我們利用了數據在內存中的自然對齊特性。
假設系統中整型數據和指針大小均為 4 字節。
則指針的數值(即其中包含的地址值),都是4的整數倍,也就是說其二進制數都是以 00 結尾。那么這 2 比特沒有承載任何信息。所以就有人腦動大開,利用這兩個比特存點信息,在使用指針之前用位操作的方式存儲2bit信息到此指針,當要對指針進行解引用操作時,把其原先值提取出來。
~~~
void put_data(int *p, unsigned int data)
{
assert(data < 4);
*p |= data;
}
unsigned int get_data(unsigned int p)
{
return (p & 3);
}
void cleanse_pointer(int *p)
{
*p &= ~3;
}
int main(void)
{
unsigned int x = 701;
unsigned int p = (unsigned int) &x;
printf("Original ptr: %un", p);
//把3存儲到指針中
put_data(&p, 3);
printf("ptr with data: %un", p);
printf("data stored in ptr: %un", get_data(p)); //獲取指針中的數據3
cleanse_pointer(&p); //在解引用指針前,把隱藏的2bit數據抹掉,恢復其原值
printf("Cleansed ptr: %un", p);
printf("Dereferencing cleansed ptr: %un", *(int*)p);
return 0;
}
~~~
這也太變態了吧,連這2個bit都不放過,現在是21世紀了,我們還缺這點內存么?
不過,在實際中還真有應用:Linux 內核中紅黑樹的實現。
樹結點定義:
struct rb_node {
? unsigned long ?__rb_parent_color;
? struct rb_node *rb_right;
? struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
此處 unsigned long __rb_parent_color 存儲了如下信息:
父節點的地址,結點的顏色
色彩的表示用 0 代表紅色,1 代表黑色。
和前面的例子一樣,該數據隱藏在父指針“無用的”比特位中。
父指針和色彩信息的獲取:
/* in rbtree.h */
#define rb_parent(r) ? ? ? ? ?**((struct rb_node *)((r)->__rb_parent_color & ~3))**
/* in rbtree_augmented.h */
#define ?__rb_color(pc) ? ?**((pc) & 1)**
#define ?rb_color(rb) ? ? ? ?**__rb_color((rb)->__rb_parent_color)**
【參考】
http://blog.jobbole.com/16035/