2012-10-08 53 views
15

每個C程序員可以決定在與該公知的宏數組中元素的數目:可靠地確定數組中的元素的數量

#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) 

下面是一個典型的使用情況:

int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19}; 
printf("%lu\n", NUM_ELEMS(numbers));   // 8, as expected 

然而,沒有什麼能夠阻止意外傳遞而不是數組的指針程序員:

int * pointer = numbers; 
printf("%lu\n", NUM_ELEMS(pointer)); 

在我的系統上,這會打印2,因爲顯然,指針的大小是整數的兩倍。我想過如何防止程序員被誤傳遞指針,我找到了一個解決方案:

#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a])) 

這工作,因爲一個指向數組的指針具有相同的值作爲一個指向它的第一個元素。如果你傳遞一個指針,指針將與一個指向它自己的指針進行比較,這個指針幾乎總是假的。 (唯一的例外是一個遞歸void指針,即,一個空指針指向本身我可以忍受的。)

不留神傳遞一個陣列的指針,而不是現在觸發在運行時的誤差:

Assertion `(void*)&(pointer) == (void*)(pointer)' failed. 

不錯!現在我有幾個問題:

  1. 是我的assert使用作爲逗號表達式有效的標準C的左操作數?也就是說,該標準是否允許我使用assert作爲表達式?對不起,如果這是一個愚蠢的問題:)

  2. 可以檢查某種方式在編譯時?

  3. 我的C編譯器認爲int b[NUM_ELEMS(a)];是一個VLA。任何方式說服他,否則?

  4. 我是第一個想到這個嗎?如果是這樣,我可以指望有多少處女在天堂等着我呢? :)

+1

至於第(4)部分,很確定它不是* 72.我認爲t帽子是其他東西的保留值... –

+0

你是不是指'sizeof a [0]'? –

+0

每一位真正的c程序員都知道,跟蹤數組的大小是他們的問題,而不是編譯器,並且「搞清楚」的技巧嚴格限制了效用,因爲這些信息只保留在範圍內。如果您希望編譯器爲您處理此問題,請使用更智能的語言。我的意思是,你只需要去C++並使用'std :: vector'或(用C++ 11)'std :: array',所以這不是一個巨大的改變。 – dmckee

回答

9

Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?

是的,這是有效的作爲逗號運算符的左操作數可以是void類型的表達式。並且assert函數具有void作爲其返回類型。

My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?

它認爲是因爲逗號表達式的結果永遠不是常量表達式(e..g,1,2不是常量表達式)。

EDIT1:在下面添加更新。

我有宏的另一個版本,在編譯時的作品:

#define NUM_ELEMS(arr)             \ 
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \ 
    + sizeof (arr)/sizeof (*(arr))) 

和這似乎與初始化與靜態存儲持續時間對象甚至還工作。 它也與您的int b[NUM_ELEMS(a)]

EDIT2的例子正常工作:

解決@DanielFischer評論。宏上述工程與gcc而不-pedantic僅因爲gcc接受:

(void *) &arr == arr 

爲整數常量表達式,而它認爲

(void *) &ptr == ptr 

不是整數常量表達式。根據C,它們都不是整數常量表達式,並且-pedanticgcc在這兩種情況下都正確地發出診斷。

據我所知,沒有100%的可移植的方式來編寫這個NUM_ELEM宏。 C使用初始化常量表達式(參見C99中的6.6p7)更靈活,可以利用它來編寫該宏(例如,使用sizeof和複合文字),但是在塊範圍內C不需要初始化器作爲常量表達式,不可能有一個在任何情況下都可以工作的宏。

EDIT3:

我認爲這是值得一提的是,Linux內核有一個ARRAY_SIZE宏(在include/linux/kernel.h)實現這種稀疏(內核靜態分析軟件)正在執行時進行檢查。

他們的解決方案是不可移植和利用兩個GNU擴展:

  • typeof操作
  • __builtin_types_compatible_p內置函數

基本上它看起來像這樣的事情:

#define NUM_ELEMS(arr) \ 
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \ 
    + sizeof (arr)/sizeof (*(arr))) 
+0

太棒了!我會給你一半的處女,如果你沒事的話。 – fredoverflow

+0

不幸的是,用'-pedantic-errors',我得到'錯誤:位域'not_an_array'寬度不是整數常量表達式[-pedantic]'來自gcc,clang默認給出錯誤:( –

+0

@DanielFischer看到我的第二個編輯該地址您的評論 – ouah

3
  1. 是的。逗號運算符的左表達式總是作爲空表達式計算(C99 6.5.17#2)。由於assert()是一個無效的表達式,所以沒有問題。
  2. 也許吧。儘管C預處理器不知道類型和強制類型,但無法比較地址,因此可以使用與編譯時評估sizeof()相同的技巧。聲明一個數組,其維數是一個布爾表達式。如果爲0,則違反約束條件,必須發佈診斷。我在這裏嘗試過,但迄今爲止還沒有成功......也許答案實際上是「不」。
  3. 編號(指針類型)不是整型常量表達式。
  4. 可能不是(在太陽下這些天沒有新的東西)。不確定性的處女的不確定數目:-)