拒绝指针的数组大小的宏

nne*_*neo 49 c arrays macros c-preprocessor

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

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
Run Code Online (Sandbox Code Playgroud)

或一些等效的形成.然而,当传入指针时,这种事情会默默地成功,并且在运行时看起来似乎有道理,直到事情神秘地分崩离析.

犯这个错误太容易了:一个具有局部数组变量的函数被重构,将一些数组操作移动到一个以数组作为参数调用的新函数中.

所以,问题是:是否有一个"卫生"宏来检测ARRAYSIZEC中宏的滥用,最好是在编译时?在C++中,我们只使用专门用于数组参数的模板; 在C中,似乎我们需要一些方法来区分数组和指针.(例如,如果我想拒绝数组,我只是(arr=arr, ...)因为数组赋值是非法的).

oua*_*uah 31

Linux内核使用一个很好的实现ARRAY_SIZE来处理这个问题:

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
Run Code Online (Sandbox Code Playgroud)

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
Run Code Online (Sandbox Code Playgroud)

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
Run Code Online (Sandbox Code Playgroud)

当然,这只能在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)))
Run Code Online (Sandbox Code Playgroud)

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e)  \
    (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))
Run Code Online (Sandbox Code Playgroud)

随着gcc如果参数是一个数组这个没有给出警告,-std=c99 -Wall-pedantic会发出警告.原因是IS_ARRAYexpression不是整型常量表达式(在整型常量表达式中不允许转换为指针类型和下标运算符),并且位域宽度in STATIC_EXP需要整数常量表达式.

  • 哦,太好了,这是一个宝石。我应该认为 Linux 内核开发人员会解决这个问题。 (2认同)

Dav*_*eri 17

这个版本ARRAYSIZE()返回0arr是一个指针和尺寸当其纯阵列

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

5
0
10
Run Code Online (Sandbox Code Playgroud)

正如本杰克逊所指出的,你可以强制运行时异常(除以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))
Run Code Online (Sandbox Code Playgroud)

遗憾的是,你不能强制编译时错误(arg必须在运行时比较地址)

  • @DigitalTrauma,因为当参数不是数组(或指针)时会引发错误.`error:下标值既不是数组也不是指针,也不是vector` (3认同)

blu*_*uss 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));
Run Code Online (Sandbox Code Playgroud)

宏检查:1)指向A的指针不是指向指针的指针.2)指向elem的指针指向T.它(void)0使用指针进行静态求值和失败.

这是一个不完美的答案,但也许读者可以改进它并摆脱该类型参数!


456*_*976 5

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

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))
Run Code Online (Sandbox Code Playgroud)