每个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]))
这样做是因为一个指针阵列具有相同的值作为一个指针,它的第一个元素。 如果你传递一个指针代替,指针将指向自己的指针,这几乎总是错误的进行比较。 (唯一的例外是一个递归空指针,就是一个空指针指向自己。我可以忍受的。)
意外地传递指针而不是数组的现在在运行时触发一个错误:
Assertion `(void*)&(pointer) == (void*)(pointer)' failed.
太好了! 现在我有几个问题:
是我的使用assert
作为逗号表达式有效的标准C的左操作数? 也就是说,它的标准允许我使用assert
作为一种表达? 很抱歉,如果这是一个愚蠢的问题:)
可以检查以某种方式在编译时做什么?
我的C编译器认为int b[NUM_ELEMS(a)];
是VLA。 没有办法说服他,否则?
我是第一个想到的呢? 如果是这样,有多少处女我能期待在天堂在等着我呢? :)
我断言的使用作为逗号表达式有效的标准C的左操作数? 也就是说,它的标准允许我使用断言作为一种表达?
是的,它是有效的作为逗号运算符的左操作数可以是类型的表达式void
。 并assert
功能具有void
的返回类型。
我的C编译器认为INT B [NUM_ELEMS(A)]; 是VLA。 没有办法说服他,否则?
它认为是因为一个逗号表达式的结果是从来没有的常量表达式(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他们都没有整型常量表达式,并与-pedantic
, gcc
正确问题在这两种情况下的诊断。
据我所知,写这没有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)))