是否有一种类型安全的方法来获取C中的数组元素?

GMa*_*ckG 5 c arrays c89 visual-c++

在C中获取数组元素的通常方法是这样的:

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

这导致了一个整数常量表达式,这也是一个非常好的加法.

问题是它不是类型安全的:int* i; COUNTOF(i); /* compiles :( */.在实践中,这应该很少出现,但为了正确起见,使这种类型安全是很好的.


在C++ 03中这很容易(在C++ 11中它更容易,留给读者练习):

template <typename T, std::size_t N>
char (&countof_detail(T (&)[N]))[N]; // not defined

#define COUNTOF(arr) (sizeof(countof_detail(arr)))
Run Code Online (Sandbox Code Playgroud)

这使用模板推导来获取N数组的大小,然后将其编码为类型的大小.

但是在C中我们没有得到那种语言功能.这是我做的小框架:

// if `condition` evaluates to 0, fails to compile; otherwise results in `value`
#define STATIC_ASSERT_EXPR(condition, value) \
        (sizeof(char[(condition) ? 1 : -1]), (value))

// usual type-unsafe method
#define COUNTOF_DETAIL(arr) (sizeof(arr) / sizeof(arr[0]))

// new method:
#define COUNTOF(arr)                            \
        STATIC_ASSERT_EXPR(/* ??? */,           \
                           COUNTOF_DETAIL(arr)) \
Run Code Online (Sandbox Code Playgroud)

我可以投入什么/* ??? */来实现我想要的行为?或者这不可能吗?

我更倾向于在MSVC(即C89)中使用答案,但为了好奇,任何明确的答案都可以.

Jos*_*sey 1

这是我的第二个答案。它给出了两种解决方案。

第一个解决方案需要 gcc 扩展;OP确实说过他更喜欢在MSVC中有效的答案,但“任何明确的答案都可以”。

第二种解决方案借鉴了 ouah /sf/answers/894903761/的优秀答案,并且可能更便携。

我们从经典的定义开始:

#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0])) // signed is optional
Run Code Online (Sandbox Code Playgroud)

对于第一个解决方案,在 gcc 中,您可以进行测试以确定是否有任何表达式计算结果为数组(或者它在 处给出编译错误(x)[0]);我已经用 6 年前的 gcc 4.1.2 测试了这个解决方案:

#define NUMBER(x) __builtin_choose_expr(                      \
   __builtin_types_compatible_p(typeof(x), typeof((x)[0])[]), \
   NUMBER_naive(x), garbage_never_defined)
extern void *garbage_never_defined;
Run Code Online (Sandbox Code Playgroud)

第二个解决方案是:

#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // BUILD_BUG_ON_ZERO()
#define NUMBER(x) (NUMBER_naive(x) * !ASSERT_zero((void *)&(x) == (x)))
Run Code Online (Sandbox Code Playgroud)

以下是一个简短的测试程序,针对一些示例数组和指针:

#include <stdio.h>
#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // BUILD_BUG_ON_ZERO()
#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0]))
#define NUMBER(x) (NUMBER_naive(x) * !ASSERT_zero((void*)&(x) == (x)))

int a1[10];
extern int a2[];
extern int a3[10];
int *p;
int square[10][10];

static void foo(int param[10]) {
// printf("foo param    %d\n", NUMBER(param));
}
static void bar(int param[][10]) {
// printf("bar param    %d\n", NUMBER(param));
   printf("bar param[0] %d\n", NUMBER(param[0]));
   printf("bar *param   %d\n", NUMBER(*param));
}
int main(void) {
   printf("a1 %d\n", NUMBER(a1));
// printf("a2 %d\n", NUMBER(a2));
   printf("a3 %d\n", NUMBER(a3));
// printf("p  %d\n", NUMBER(p));
   printf("square  %d\n", NUMBER(square));
   printf("*square %d\n", NUMBER(*square));
   foo(a1);
   bar(square);
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

这给出:

a1 10
a3 10
square  10
*square 10
bar param[0] 10
bar *param   10
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我注释掉了四行不会或不应该编译的行,三行用于三个指针,一行用于不完整的数组类型。

我在选择第三个参数时遇到了一个小问题__builtin_types_compatible_p()gcc 手册(正确地)指出"Furthermore, the unused expression (exp1 or exp2 depending on the value of const_exp) may still generate syntax errors.",所以现在我将其设置为从未实例化的变量,garbage_never_defined因此对于某些四个注释掉的行,我们得到编译器警告和链接器错误,而不是编译错误。