Reliably determine the number of elements in an array

孤街醉人 提交于 2019-11-27 08:39:40
ouah

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?

Yes, it is valid as the left operand of the comma operator can be an expression of type void. And assert function has void as its return type.

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

It believes so because the result of a comma expression is never a constant expression (e..g, 1, 2 is not a constant expression).

EDIT1: add the update below.

I have another version of your macro which works at compile time:

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

and which seems to work even also with initializer for object with static storage duration. And it also work correctly with your example of int b[NUM_ELEMS(a)]

EDIT2:

to address @DanielFischer comment. The macro above works with gcc without -pedantic only because gcc accepts :

(void *) &arr == arr

as an integer constant expression, while it considers

(void *) &ptr == ptr

is not an integer constant expression. According to C they are both not integer constant expressions and with -pedantic, gcc correctly issues a diagnostic in both cases.

To my knowledge there is no 100% portable way to write this NUM_ELEM macro. C has more flexible rules with initializer constant expressions (see 6.6p7 in C99) which could be exploited to write this macro (for example with sizeof and compound literals) but at block-scope C does not require initializers to be constant expressions so it will not be possible to have a single macro which works in all cases.

EDIT3:

I think it is worth mentioning that the Linux kernel has an ARRAY_SIZE macro (in include/linux/kernel.h) that implements such a check when sparse (the kernel static analysis checker) is executed.

Their solution is not portable and make use of two GNU extensions:

  • typeof operator
  • __builtin_types_compatible_p builtin function

Basically it looks like something like that:

#define NUM_ELEMS(arr)  \
 (sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));})  \
  + sizeof (arr) / sizeof (*(arr)))
  1. Yes. The left expression of a comma operator is always evaluated as a void expression (C99 6.5.17#2). Since assert() is a void expression, no problem to begin with.
  2. Maybe. While the C preprocessor doesn't know about types and casts and can't compare addresses you can use the same trick as for evaluating sizeof() at compile time, e.g. declaring an array the dimension of which is a boolean expression. When 0 it is a constraint violation and a diagnostic must be issued. I've tried it here, but so far have not been successful... maybe the answer actually is "no".
  3. No. Casts (of pointer types) are not integer constant expressions.
  4. Probably not (nothing new under the Sun these days). An indeterminate number of virgins of indeterminate sex :-)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!