在C(而不是C++)中实现编译时静态断言的最佳方法是什么,特别强调GCC?
我已经看到了几个浮动数组长度的宏:
从这个问题:
#define length(array) (sizeof(array)/sizeof(*(array)))
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
#define SIZE(array, type) (sizeof(array) / (sizeof(type))
和Visual Studio的_countof
:
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
Run Code Online (Sandbox Code Playgroud)
我想知道的是:
array[0]
和有*array
什么区别?以下程序在gcc 4.8.2上两次打印相同的数字:
#include <stdio.h>
int main()
{
char a[13];
printf("sizeof a is %zu\n", sizeof a );
printf("sizeof(a) is %zu\n", sizeof(a));
}
Run Code Online (Sandbox Code Playgroud)
根据这篇reddit帖子,gcc在这方面并不符合标准,因为当数组到指针衰减没有发生时,括号表达式不在异常列表中.
这家伙是对的吗?这是相关的标准报价:
除非它是运算
sizeof
符或一元运算&
符的操作数,或者是用于初始化字符数组数组的字符串文字,或者是用于初始化与元素类型兼容的数组的宽字符串文字,否则具有wchar_t
的左值类型'array of type'被转换为一个表达式,其类型为'指向类型的指针',指向数组对象的初始成员而不是左值.
为了清楚起见,他认为(a)
应该触发数组到指针的衰减,因为上面的列表中没有括号括起来(sizeof
运算符,一元运算&
符,字符串文字作为初始值).
每个C程序员都可以使用这个众所周知的宏来确定数组中元素的数量:
#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a])
Run Code Online (Sandbox Code Playgroud)
这是一个典型的用例:
int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19};
printf("%lu\n", NUM_ELEMS(numbers)); // 8, as expected
Run Code Online (Sandbox Code Playgroud)
但是,没有什么能阻止程序员意外地传递指针而不是数组:
int * pointer = numbers;
printf("%lu\n", NUM_ELEMS(pointer));
Run Code Online (Sandbox Code Playgroud)
在我的系统上,这打印2,因为显然,指针是整数的两倍.我想过如何防止程序员错误地传递指针,我找到了一个解决方案:
#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a]))
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为指向数组的指针与指向其第一个元素的指针具有相同的值.如果改为传递指针,指针将与指向自身的指针进行比较,这几乎总是假的.(唯一的例外是递归的void指针,也就是指向自身的void指针.我可以忍受它.)
意外地传递指针而不是数组现在在运行时触发错误:
Assertion `(void*)&(pointer) == (void*)(pointer)' failed.
Run Code Online (Sandbox Code Playgroud)
太好了!现在我有几个问题:
我的用法是assert
逗号表达式的左操作数有效标准C吗?也就是说,标准是否允许我assert
用作表达式?对不起,如果这是一个愚蠢的问题:)
可以在编译时以某种方式完成检查吗?
我的C编译器认为这int b[NUM_ELEMS(a)];
是一个VLA.有没有办法说服他呢?
我是第一个想到这个吗?如果是这样,我可以期待在天堂等待多少处女?:)
查找静态数组的大小是一种常见操作.看:C找到静态数组大小 -sizeof(a) / sizeof((a)[0])
这可以包装成一个宏,例如:
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
但是它可能会意外地传入常规指针.
例如: void func(SomeArray **foo) { int i = ARRAY_SIZE(foo); }
虽然它的有效C,但往往最终成为一个逻辑错误.
它可以防止这种错误(利用每个处理器在零长度位域上失败).
#define ARRAY_SIZE(a) \
((sizeof(struct { int isnt_array : \
((const void *)&(a) == &(a)[0]); }) * 0) + \
(sizeof(a) / sizeof(*(a))))
Run Code Online (Sandbox Code Playgroud)
我发现这个宏适用于GCC,但是对于间接引用的成员,它与Clang失败了.同error: expression is not an integer constant expression
例如:
char word[8]; int i = ARRAY_SIZE(word);
好.struct Bar { word[8]; }
void func(struct Bar *foo) { int i = ARRAY_SIZE(foo->word); …
用于计算数组中元素数量的经典宏是:
#define countof(a) (sizeof(a) / sizeof(*(a)))
Run Code Online (Sandbox Code Playgroud)
这个问题是如果参数是指针而不是数组,它会无声地失败.是否有一种可移植的方法来确保此宏仅与实际数组一起使用,如果a
不是数组则生成编译时错误?
编辑:我的问题似乎是这个问题的重复:拒绝指针的数组大小的宏
在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) 有没有办法在ac宏的编译时验证参数是一个数组?
例如在这两个宏中:
#define CLEAN_ARRAY(arr) \
do { \
bzero(arr, sizeof(arr)); \
} while (0)
Run Code Online (Sandbox Code Playgroud)
和
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
Run Code Online (Sandbox Code Playgroud)
我尝试使用CTC(X)
宏,但无法找到任何方法来验证/警告是否arr
不是一个数组.
目前,我有以下代码块来进行安全字符串复制(它的工作原理):
#define STRCPY(dst, src) do { assert(((void*)(dst)) == ((void*) & (dst))); \
strlcpy(dst, src, sizeof(dst)); } while (0)
Run Code Online (Sandbox Code Playgroud)
所以它接受如下构造:
const char *src = "hello";
char dest[5];
STRCPY(dest, src); //hell
Run Code Online (Sandbox Code Playgroud)
并否认以下内容:
void (char *dst) {
STRCPY(dst, "heaven"); //unknown size of dst
}
Run Code Online (Sandbox Code Playgroud)
问题是代码块会产生一个断言.有没有办法在编译时执行此检查?
所以我想在编译时遇到错误(比如创建一个负大小的数组),而不是在可能的情况下崩溃代码.
c ×11
arrays ×8
assert ×2
c++ ×2
gcc ×2
sizeof ×2
assertion ×1
c89 ×1
compile-time ×1
memory ×1
parentheses ×1
pointers ×1
visual-c++ ×1