C99中很酷的C語言技巧
C語言常常讓人覺得它所能表達的東西非常有限。它不具有類似第一級函式和模式匹配這樣的高階功能。但是C非常簡單,並且仍然有一些非常有用的語法技巧和功能,只是沒有多少人知道罷了。
指定的初始化
很多人都知道像這樣來靜態地初始化陣列:
int
fibs[] = {1, 1, 2, 3, 5};
C99標準實際上支援一種更為直觀簡單的方式來初始化各種不同的集合類資料(如:結構體,聯合體和陣列)。
(keil如何支援C99?——在PROJECT 選項中將C/C++的MISC CONTROL加上--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。
結構體與聯合體
1 |
用結構體與聯合體的欄位名稱來初始化資料是非常有用的。假設我們定義:
|
struct
point {
int
x;
int
y;
int
z;
}
然後我們這樣初始化
struct
point:
struct
point p = {.x = 3, .y = 4, .z = 5};
當我們不想將所有欄位都初始化為0時,這種作法可以很容易的在編譯時就生成結構體,而不需要專門呼叫一個初始化函式。
對聯合體來說,我們可以使用相同的辦法,只是我們只用初始化一個欄位。
巨集列表
C中的一個慣用方法,是說有一個已命名的實體列表,需要為它們中的每一個建立函式,將它們中的每一個初始化,並在不同的程式碼模組中擴充套件它們的名字。 這在Mozilla的原始碼中經常用到,我就是在那時學到這個技巧的。例如,在我去年夏天工作的那個專案中,我們有一個針對每個命令進行標記的巨集列表。其工 作方式如下:
#define FLAG_LIST(_) \
_(InWorklist) \
_(EmittedAtUses) \
_(LoopInvariant) \
_(Commutative) \
_(Movable) \
_(Lowered) \
_(Guard)
它定義了一個FLAG_LIST巨集,這個巨集有一個引數稱之為 _ ,這個引數本身是一個巨集,它能夠呼叫列表中的每個引數。舉一個實際使用的例子可能更能直觀地說明問題。假設我們定義了一個巨集DEFINE_FLAG,如:
#define DEFINE_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
一步步的展示其過程是非常有啟發性的,如果對它的使用還有不解,可以花一些時間在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 } )
因為位寬為負,所以可以確定,如果標誌的數量超過了我們指派的空間,那麼編譯將會失敗。