69

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

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

或一些等效的形式。然而,当一个指针被传入时,这种事情会默默地成功,并给出在运行时看似合理的结果,直到事情神秘地分崩离析。

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

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

4

9 回答 9

47

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需要整数常量表达式。

于 2013-10-18T17:01:34.263 回答
20

这个版本的ARRAYSIZE()返回0whenarr是一个指针,当它是一个纯数组时返回大小

#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必须在运行时比较的地址)

于 2013-10-18T15:50:57.493 回答
6

使用 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并失败。

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

于 2014-05-13T12:31:03.633 回答
6

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

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))
于 2015-02-14T17:15:05.870 回答
1

这是一种使用称为语句表达式的 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 扩展的平台。在这些情况下,我建议只使用标准宏,而不必担心意外传入指向宏的指针。

于 2013-10-18T15:21:50.323 回答
1

这是另一个依赖 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')
于 2013-10-18T15:40:37.553 回答
0

太糟糕了,是的,但这很有效,而且它是便携的。

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

这不会在编译时检测到任何东西,但会打印出一条错误消息,如果它是指针数组长度为 1,则stderr返回。-1

==>演示<==

于 2013-10-18T15:53:48.340 回答
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"
于 2015-06-16T06:59:41.953 回答
0

该系列的另一个例子。

#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 扩展:TypeofStatement Exprs和(如果你喜欢的话)条件
  2. 这取决于 C99 VLA功能
于 2017-12-04T00:43:30.983 回答