可靠地确定数组中元素的数目(Reliably determine the number of ele

2019-06-18 11:34发布

每个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.

太好了! 现在我有几个问题:

  1. 是我的使用assert作为逗号表达式有效的标准C的左操作数? 也就是说,它的标准允许我使用assert作为一种表达? 很抱歉,如果这是一个愚蠢的问题:)

  2. 可以检查以某种方式在编译时做什么?

  3. 我的C编译器认为int b[NUM_ELEMS(a)]; 是VLA。 没有办法说服他,否则?

  4. 我是第一个想到的呢? 如果是这样,有多少处女我能期待在天堂在等着我呢? :)

Answer 1:

我断言的使用作为逗号表达式有效的标准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他们都没有整型常量表达式,并与-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)))


Answer 2:

  1. 是。 逗号运算符左侧表达总是评价为void表达式(C99 6.5.17#2)。 由于assert()是空隙表达,没有问题开始。
  2. 也许。 而C预处理器不知道的类型和管型和不能比较地址可以使用相同的特技作为用于评价的sizeof()在编译时,例如声明数组的维数,其中是一个布尔表达式。 当0是违反约束和诊断必须发出。 我试着在这里,但到目前为止还没有成功......也许答案居然是“不”。
  3. 号类型转换(指针类型的)不整数常量表达式。
  4. 也许不是(太阳这些天之下并无新事)。 不确定性的处女数量不确定的:-)


文章来源: Reliably determine the number of elements in an array