经常教授的标准数组大小是
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
或一些等同的形成。然而,当传入指针时,这种事情会默默地成功,并且在运行时看起来似乎有道理,直到事情神秘地崩溃。
犯这个错误太容易了:一个有局部数组变量的函数被重构,将一些数组操作移动到一个以数组作为参数调用的新函数中。
所以,问题是:是否有一个“卫生”宏来检测C中ARRAYSIZE
宏的滥用,最好是在编译时?在C ++中,我们只使用专门用于数组参数的模板;在C中,似乎我们需要一些方法来区分数组和指针。 (例如,如果我想拒绝数组,我只会做(arr=arr, ...)
,因为数组赋值是非法的。)
答案 0 :(得分:27)
Linux内核使用ARRAY_SIZE
的一个很好的实现来处理这个问题:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
与
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
和
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
当然,这只能在GNU C中移植,因为它使用了两个内在函数:
typeof
运算符和__builtin_types_compatible_p
函数。它还使用了他们的“着名”BUILD_BUG_ON_ZERO
宏,它只在GNU C中有效。
假设编译时评估要求(这是我们想要的),我不知道这个宏的任何可移植实现。
“半便携式”实施(并不涵盖所有情况)是:
#define ARRAY_SIZE(arr) \
(sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))
与
#define IS_ARRAY(arr) ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e) \
(0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))
使用gcc
如果参数是-std=c99 -Wall
中的数组但-pedantic
会发出警告,则不会发出警告。原因是IS_ARRAY
表达式不是整型常量表达式(在整数常量表达式中不允许转换为指针类型和下标运算符),STATIC_EXP
中的位域宽度需要整数常量表达式。 / p>
答案 1 :(得分:17)
当ARRAYSIZE()
是指针时,此版本的0
会返回arr
,而当它是纯数组时会返回大小
#include <stdio.h>
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)
int main(void)
{
int a[5];
int *b = a;
int n = 10;
int c[n]; /* a VLA */
printf("%zu\n", ARRAYSIZE(a));
printf("%zu\n", ARRAYSIZE(b));
printf("%zu\n", ARRAYSIZE(c));
return 0;
}
输出:
5
0
10
正如本杰克逊所指出的,你可以强制运行时异常(除以0)
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))
遗憾的是,您无法强制发生编译时错误(必须在运行时比较arg
的地址)
答案 2 :(得分:5)
使用C11,我们可以使用_Generic
来区分数组和指针,但是如果您提供元素类型,我只找到了一种方法:
#define ARRAY_SIZE(A, T) \
_Generic(&(A), \
T **: (void)0, \
default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))
int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));
宏检查:1)指向A的指针不是指向指针的指针。 2)指向elem的指针指向T。它的计算结果为(void)0
,并且通过指针静态失败。
这是一个不完美的答案,但也许读者可以改进它并摆脱那个类型参数!
答案 3 :(得分:5)
使用typeof而不是类型参数修改bluss的答案:
#define ARRAY_SIZE(A) \
_Generic(&(A), \
typeof((A)[0]) **: (void)0, \
default: sizeof(A) / sizeof((A)[0]))
答案 4 :(得分:2)
这是另一个依赖于gcc typeof extension:
的人#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
sizeof(arr) / sizeof(arr[0]);})
这可以通过尝试设置一个相同的对象并使用指定初始化程序的数组进行初始化来实现。如果传递了一个数组,那么编译器很高兴。如果指针被传递,编译器会抱怨:
arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')
答案 5 :(得分:1)
这是使用名为statement expressions的GNU扩展的一种可能的解决方案:
#define ARRAYSIZE(arr) \
({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
sizeof(arr) / sizeof((arr)[0]);})
这使用static assertion来声明sizeof(arr) != sizeof(void*)
。这有一个明显的限制 - 你不能在大小恰好是一个指针的数组上使用这个宏(例如一个长度为1的指针/整数数组,或者32位的字节数为4个字节的数组)平台)。但是这些特定的实例可以很容易地解决。
此解决方案无法移植到不支持此GNU扩展的平台。在这些情况下,我建议只使用标准宏,而不用担心意外地将指针传递给宏。
答案 6 :(得分:1)
太可怕了,是的,但这很有效,而且很便携。
#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
(sizeof(arr)/sizeof(*arr)) : \
-1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))
这不会在编译时检测到任何内容,但会在stderr
中打印出错误消息,如果数组长度为1,则返回-1
如果是指针或
<强> ==&GT; DEMO&lt; ==
答案 7 :(得分:0)
我个人最喜欢的,尝试过gcc 4.6.3和4.9.2:
#define STR_(tokens) # tokens
#define ARRAY_SIZE(array) \
({ \
_Static_assert \
( \
! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
"ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
); \
sizeof(array) / sizeof((array)[0]); \
})
/*
* example
*/
#define not_an_array ((char const *) "not an array")
int main () {
return ARRAY_SIZE(not_an_array);
}
编译器打印
x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"
答案 8 :(得分:0)
该集合的另一个例子。
#define LENGTHOF(X) ({ \
const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
typeof(X[0]) (*should_be_an_array)[length] = &X; \
length; })
<强>优点:强>
<强>缺点:强>