c-拒绝指针的数组大小的宏
通常讲授的标准数组大小宏是
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
或某些等效形式。 但是,当传入一个指针时,这种事情会悄无声息地成功,并且给出的结果在运行时看起来似乎是合理的,直到事情神秘地消失。
犯这样的错误太容易了:重构具有局部数组变量的函数,将一些数组操作移到以数组为参数的新函数中。
因此,问题是:是否存在“卫生”宏来检测C中ARRAYSIZE宏的滥用,最好是在编译时? 在C ++中,我们将仅使用专门用于数组参数的模板。 在C语言中,似乎我们需要某种方式来区分数组和指针。 (例如,如果我想拒绝数组,我只会做例如(arr=arr, ...),因为数组分配是非法的)。
9个解决方案
29 votes
Linux内核使用gcc的良好实现来处理此问题:
#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中是可移植的,因为它使用了两个内在原理: gcc操作员和-std=c99 -Wall功能。 它还使用它们的“著名的” -pedantic宏,该宏仅在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);}))
如果参数是-std=c99 -Wall中的数组,则使用gcc不会给出警告,但是-pedantic会给出警告。 原因是IS_ARRAY表达式不是整数常量表达式(在整数常量表达式中不允许向指针类型和下标运算符进行转换),并且STATIC_EXP中的位字段宽度需要整数常量表达式。
ouah answered 2020-06-17T03:25:35Z
17 votes
当arr是指针时,此版本的arg返回0,当其为纯数组时返回大小
#include
#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
正如本杰克逊(Ben Jackson)指出的那样,您可以强制运行时异常(除以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的地址)
David Ranieri answered 2020-06-17T03:26:09Z
5 votes
使用C11,我们可以使用(void)0区分数组和指针,但是只有在提供元素类型的情况下,我才找到一种方法:
#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)指向元素的指针是指向T的指针。 计算结果为(void)0,并使用指针静态失败。
这不是一个完美的答案,但也许读者可以改进它并摆脱该类型参数!
bluss answered 2020-06-17T03:26:38Z
5 votes
使用typeof而不是type参数修改bluss的答案:
#define ARRAY_SIZE(A) \
_Generic(&(A), \
typeof((A)[0]) **: (void)0, \
default: sizeof(A) / sizeof((A)[0]))
4566976 answered 2020-06-17T03:26:58Z
2 votes
这是另一个依赖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')
Digital Trauma answered 2020-06-17T03:27:23Z
1 votes
这是使用称为语句表达式的GNU扩展的一种可能的解决方案:
#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*)。这有一个明显的局限性-您不能在大小恰好是一个指针的数组上使用此宏(例如,一个1长度的指针/整数数组,或者可能是4 -在32位平台上的字节长度数组)。 但是这些特定实例可以很容易地解决。
该解决方案无法移植到不支持此GNU扩展的平台上。 在这种情况下,我建议您仅使用标准宏,而不必担心会意外地将指针传递给该宏。
Adam Rosenfield answered 2020-06-17T03:27:52Z
1 votes
糟透了,是的,但这行得通,而且可移植。
#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。
==>演示<==
Michael M. answered 2020-06-17T03:28:21Z
0 votes
我个人最喜欢的,尝试了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"
not-a-user answered 2020-06-17T03:28:45Z
0 votes
集合的另一个例子。
#define LENGTHOF(X) ({ \
const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
typeof(X[0]) (*should_be_an_array)[length] = &X; \
length; })
优点:
它适用于普通数组,可变长度数组,多维数组,零大小结构的数组
如果您传递任何指针,struct或,它将生成一个编译错误(不是警告)。联盟
它不依赖于C11的任何功能
它给你非常可读的错误
缺点:
这取决于某些gcc扩展:Typeof,语句表达式,以及(如果您喜欢)条件条件
这取决于C99 VLA功能
hurufu answered 2020-06-17T03:29:40Z