拒绝指针的数组大小宏

通常教授的标准数组大小宏是

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))

或者类似的阵型。然而,当一个指针被传入时,这种事情就会悄无声息地成功,并在运行时产生看似合理的结果,直到事情莫名其妙地崩溃。

这个错误太容易犯了: 一个具有本地数组变量的函数被重构,将一些数组操作移动到一个新函数中,该函数使用数组作为参数。

因此,问题是: 是否有一个“卫生的”宏来检测 C 语言中 ARRAYSIZE宏的错误使用,最好是在编译时?在 C + + 中,我们只使用一个专门用于数组参数的模板; 在 C 中,我们似乎需要一些方法来区分数组和指针。(例如,如果我想拒绝数组,我只需要执行例如 (arr=arr, ...)的操作,因为数组赋值是非法的)。

18418 次浏览

下面是使用名为 语句表达式的 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的指针/整数数组,或者在32位平台上一个长度为4的字节数组)。但是这些特殊的实例可以很容易地处理。

此解决方案不能移植到不支持此 GNU 扩展的平台。在这种情况下,我建议只使用标准的宏,不要担心意外地传入指向宏的指针。

这是另一个依赖于 扩展类型扩展类型扩展类型的方法:

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

这个版本的 ARRAYSIZE()arr是指针时返回 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

正如 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的地址)

可怕,是的,但是这个工作,它是便携式的。

#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。

= = > 演示 = =

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中的位域宽度需要一个整数常量表达式。

使用 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)指针到元素是指针到 T。它的计算结果为 (void)0,并且使用指针静态失败。

这是一个不完美的答案,但也许读者可以对它进行改进,去掉那个类型参数!

使用 typeof 而不是 type 参数修改 bluss 的答案:

#define ARRAY_SIZE(A) \
_Generic(&(A), \
typeof((A)[0]) **: (void)0, \
default: sizeof(A) / sizeof((A)[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"

集合中的另一个示例。

#define LENGTHOF(X) ({ \
const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
typeof(X[0]) (*should_be_an_array)[length] = &X; \
length; })

优点:

  1. 它可以处理普通数组、可变长度数组和多维数组 数组,零大小结构的数组
  2. 如果传递任何指针、结构体或 工会
  3. 它不依赖于 C11的任何特性
  4. 它给出了非常易读的错误

缺点:

  1. 它取决于一些 gcc 扩展: 类型, 语句 Exprs 和(如果您喜欢) < a href = “ https://gcc.gnu.org/onlinedocs/gcc/Conditionals.html”rel = “ nofollow noReferrer”> Conditionals
  2. 这取决于 C99 VLA的功能