拒绝指针的数组大小的macros
经常教授的标准数组大小的macros是
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
或者一些等同的forms。 然而,当一个指针被传入时,这种事情默默地成功了,并且给出了在运行时似乎是合理的结果,直到事情神秘崩溃。
这太容易犯这个错误了:一个具有本地数组variables的函数被重构,将一些数组操作转移到一个新的函数中,并以该数组作为参数调用。
所以,问题是:是否有一个“卫生”的macros来检测C语言中的ARRAYSIZE
macros的滥用,最好在编译时? 在C ++中,我们只使用专门用于数组参数的模板; 在C中,似乎我们需要一些方法来区分数组和指针。 (如果我想拒绝数组,例如,我只是做例如(arr=arr, ...)
因为数组分配是非法的)。
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
macros,它只在GNU C中有效。
假设编译时评估需求(这是我们想要的),我不知道这个macros的任何可移植的实现。
“半便携式”实施(并不涵盖所有情况)是:
#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
expression式不是整型常量expression式(在整型常量expression式中不允许转换为指针types和下标运算符), STATIC_EXP
的位域宽度需要一个整型常量expression式。
当arr
是一个指针时, ARRAYSIZE()
这个版本返回0
,当它是一个纯数组的时候返回大小
#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
正如本jackson指出的,你可以强制运行时exception(除以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
的地址)
使用typeof而不是types参数修改bluss的答案:
#define ARRAY_SIZE(A) \ _Generic(&(A), \ typeof((A)[0]) **: (void)0, \ default: sizeof(A) / sizeof((A)[0]))
这是另一个依赖于gcc typeof扩展的方法 :
#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')
使用C11,我们可以使用_Generic
区分数组和指针,但是我只find了一种方法来实现它,如果您提供了元素types:
#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));
macros检查:1)指针指向A不是指针指针。 2)指向elem的指针指向T。 它的计算结果为(void)0
并用指针静态失败。
这是一个不完美的答案,但也许读者可以改进它,摆脱这种types的参数!
以下是一个使用称为语句expression式的GNU扩展的可能解决scheme:
#define ARRAYSIZE(arr) \ ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \ sizeof(arr) / sizeof((arr)[0]);})
这使用静态断言来声明sizeof(arr) != sizeof(void*)
。 这有一个明显的限制 – 你不能在大小恰好是一个指针的数组上使用这个macros(比如一个长度为一个指针/整数的数组,或者是一个32位的长度为4的数组)平台)。 但是这些特定的情况可以很容易地解决。
这个解决scheme不能移植到不支持GNU扩展的平台上。 在这种情况下,我build议只使用标准macros而不要担心不小心传递给macros的指针。
糟透了,是的,但是这个工作起来很方便。
#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
中stderr
错误信息,如果是指针或数组长度为1,则返回-1
。
==> DEMO <==
我个人最喜欢的,试了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); }
编译器打印
xc:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"