【问题标题】:Is there a type-safe way of getting an element count for arrays in C? [duplicate]是否有一种类型安全的方法来获取 C 中数组的元素计数? [复制]
【发布时间】:2012-10-11 22:44:02
【问题描述】:

在 C 中获取数组元素计数的常用方法如下:

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

这会产生一个整数常量表达式,这也是一个很好的加分项。

问题在于它不是类型安全的: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)))

这使用模板推导得到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)) \

我可以在/* ??? */ 中输入什么来获得我想要的行为?或者这是不可能的?

我更喜欢 MSVC(即 C89)中的答案,但出于好奇,任何明确的答案都可以。

【问题讨论】:

  • 如果你想要类型安全,也许 C 语言不适合你。 :P
  • @cHao:这不是我的语言。 :)
  • 我只能想办法做相反的事情,检查它是否是一个指针。
  • 以防万一它可以帮助某人想出一种相反的方法... #define COUNTOF(arr) (arr=arr)?(sizeof(arr) / sizeof(arr [0])) : 0
  • @Sebastian "sizeof 运算符不得应用于具有函数类型或不完整类型的表达式,..."。这将需要诊断,很可能是编译失败。但是,如果您将数组声明并初始化为int arr[] = {0,2,3};,它将起作用,并且应该arr 的类型为int[3]

标签: c arrays visual-c++ c89


【解决方案1】:

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

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

第二种解决方案借鉴了 ouah https://stackoverflow.com/a/12784339/318716 的出色回答,可能更便携。

我们从经典定义开始:

#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0])) // signed is optional

对于第一个解决方案,在 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;

第二种解决方法是:

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

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

#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;
}

这给出了:

a1 10
a3 10
square  10
*square 10
bar param[0] 10
bar *param   10

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

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

【讨论】:

  • 查看 cmets,我看到 nos 已经建议使用 gcc 的__builtin_types_compatible_p()。但他的评论中有一个额外的&amp;;我还没有测试过。
  • (void*)&amp;(x) == (x) 是常量表达式吗? MSVC 抱怨 !!(e) 的值需要是一个常量表达式(很明显)并且无法编译。我看到(void*)&amp;(x) == (x) 原则上可以在编译时知道,但我认为这可能不是必需的。
  • 要自我回答我的评论,事实并非如此。我相信没有通用的解决方案,因为重复的问题也表明。但是,我接受这是一个很好的近似值。谢谢。
  • 编辑:在最后一段中,“__builtin_types_compatible_p() 的第三个参数”当然应该改为“__builtin_choose_expr() 的第三个参数”。
【解决方案2】:

例子:

#include <stdio.h>

#define IS_NOT_POINTER(x)  (sizeof(x) != sizeof 42[x])
#define COUNTOF(x)         ((int)(sizeof(x) / sizeof 42[x])) // signed is convenient
#define COUNTOF_SAFE(x)    (COUNTOF(x) / IS_NOT_POINTER(x))

extern int x[10];
extern int *y;

int main(void) {
   printf("%d\n", COUNTOF(x));
   printf("%d\n", COUNTOF(y));
   printf("%d\n", COUNTOF_SAFE(x));
   printf("%d\n", COUNTOF_SAFE(y));
   return 0;
}

这会在 gcc 4.1.2 中给出编译时警告:

    foo.c:14: warning: division by zero

出于好奇,并不是我们真正关心,并且可能因版本而异,运行它会给出:

   10
   1
   10
   0

编辑:我对代码做了些微改动,删除了IS_NOT_POINTER(x) / IS_NOT_POINTER(x)。编译警告仍然存在,但现在在运行时它给出了正确的三个值,后跟Floating point exception (core dumped). 同样,我们不在乎,但这可能更好。

【讨论】:

  • 这里没有警告,输出是 10、2、10、2。不过,正如预期的那样,我收到了 long* 的警告,long x[1]; long *y; 收到了两个警告。
  • @DanielFischer:我需要将 COUNTOF 重命名为 COUNTOF_UNSAFE,然后将 COUNTOF_SAFE 重命名为 COUNTOF。然后您可以将 COUNTOF_UNSAFE 用于您的单元素数组long x[1]。但我不明白你的两个 2 是从哪里来的。
  • 八字节指针。所以int *ysizeof(y) / sizeof 42[y] = sizeof(int*) / sizeof(int) = 8/4 = 2sizeof(x) != sizeof 42[x] 为所有长度为 1 的数组生成 0,为所有 T*sizeof(T*) != sizeof(T) 生成 1,这是大多数类型,无论指针的大小如何。
  • sizeof(42[x]) 是做什么的?
  • @DanielFischer:谢谢——我还停留在 32 位世界。所以我的答案需要一些调整。而且我还没有用 MSVC 测试过。
【解决方案3】:

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

我会说,不。上面的宏很好,但只有在传递一个真实数组时才能正常工作。

宏只是为了简化您的代码,当您需要类型安全时,您不应依赖它们。如果这是你需要的,你不应该使用 C 或坚持它的规则。

【讨论】:

  • 有时这些“有原则的”答案是有道理的,但这不是其中之一。没有原则上的理由,我不能尝试既使用宏来简化代码,又以一种比天真的方式更安全的方式来简化代码。
  • 告诉我怎么知道这次不是其中之一?我的回答仍然是正确的:除非您使用一些允许您测试变量类型的编译器扩展,否则在使用宏时没有办法 100% 类型安全。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-05
  • 2014-12-19
  • 2020-11-20
  • 2020-11-20
  • 2014-11-18
  • 2018-06-27
相关资源
最近更新 更多