可靠地确定数组中的元素数量 [英] Reliably determine the number of elements in an array

查看:40
本文介绍了可靠地确定数组中的元素数量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

每个 C 程序员都可以使用这个众所周知的宏来确定数组中元素的数量:

Every C programmer can determine the number of elements in an array with this well-known macro:

#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

然而,没有什么能阻止程序员意外地传递一个指针而不是一个数组:

However, nothing prevents the programmer from accidentally passing a pointer instead of an array:

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

在我的系统上,这会打印 2,因为显然指针是整数的两倍.想了想怎么防止程序员误传指针,找到了解决办法:

On my system, this prints 2, because apparently, a pointer is twice as large as an integer. I thought about how to prevent the programmer from passing a pointer by mistake, and I found a solution:

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

这是可行的,因为指向数组的指针与指向其第一个元素的指针具有相同的值.如果您改为传递指针,则该指针将与指向自身的指针进行比较,这几乎总是错误的.(唯一的例外是递归空指针,即指向自身的空指针.我可以接受.)

This works because a pointer to an array has the same value as a pointer to its first element. If you pass a pointer instead, the pointer will be compared with a pointer to itself, which is almost always false. (The only exception is a recursive void pointer, that is, a void pointer that points to itself. I can live with that.)

意外传递指针而不是数组现在会在运行时触发错误:

Accidentally passing a pointer instead of an array now triggers an error at runtime:

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

不错!现在我有几个问题:

Nice! Now I have a couple of questions:

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

  1. 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? Sorry if this is a dumb question :)

可以在编译时以某种方式进行检查吗?

Can the check somehow be done at compile-time?

我的 C 编译器认为 int b[NUM_ELEMS(a)]; 是一个 VLA.有什么办法说服他吗?

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

我是第一个想到这个的吗?如果是这样,我可以期望在天堂等待我的处女有多少?:)

Am I the first to think of this? If so, how many virgins can I expect to be waiting for me in heaven? :)

推荐答案

我使用断言作为逗号表达式的左操作数是否有效标准 C?也就是说,标准是否允许我使用 assert 作为表达式?

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.

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.

我的 C 编译器认为 int b[NUM_ELEMS(a)];是一个 VLA.有什么办法说服他吗?

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

之所以这么认为,是因为逗号表达式的结果永远不是常量表达式(例如,1、2 不是常量表达式).

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

在下面添加更新.

我有另一个版本的宏,它可以在编译时运行:

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)))

并且它似乎也适用于具有静态存储持续时间的对象的初始值设定项.它也适用于您的 int b[NUM_ELEMS(a)]

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)]

解决@DanielFischer 评论.上面的宏适用于 gcc without -pedantic 只是因为 gcc 接受:

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

不是整数常量表达式.根据 C,它们都不是整数常量表达式,并且使用 -pedanticgcc 在这两种情况下都正确地发出诊断.

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.

据我所知,没有 100% 可移植的方式来编写这个 NUM_ELEM 宏.C 有更灵活的初始化常量表达式规则(参见 C99 中的 6.6p7),可以利用它来编写这个宏(例如使用 sizeof 和复合文字),但在块范围内 C 不需要初始化器是常量表达式,因此不可能有一个适用于所有情况的宏.

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.

我认为值得一提的是,Linux 内核有一个 ARRAY_SIZE 宏(在 include/linux/kernel.h 中),它在稀疏(内核静态分析检查器)被执行.

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.

他们的解决方案不可移植并且使用了两个 GNU 扩展:

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

  • typeof 运算符
  • __builtin_types_compatible_p 内置函数
  • 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)))

这篇关于可靠地确定数组中的元素数量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆