让编译器检查数组初始值设定项的数量 初始化一个数组(在C++中,但是对于C工作的任何解决方案也可能在这里工作),初始化器比元素完全合法: int array[10] = { 1, 2, 3 };
然而,这可能是一个来源不明的错误。有没有办法让编译器(gcc)检查一个特定数组的初始值设定项数量,如果声明的大小和实际大小不匹配,会发出警告甚至错误 我知道我可以使用让编译器检查数组初始值设定项的数量 初始化一个数组(在C++中,但是对于C工作的任何解决方案也可能在这里工作),初始化器比元素完全合法: int array[10] = { 1, 2, 3 };,c++,c,gcc,compiler-warnings,gcc-warning,C++,C,Gcc,Compiler Warnings,Gcc Warning,然而,这可能是一个来源不明的错误。有没有办法让编译器(gcc)检查一个特定数组的初始值设定项数量,如果声明的大小和实际大小不匹配,会发出警告甚至错误 我知道我可以使用intarray[]={1,2,3}
intarray[]={1,2,3}然后可以使用涉及sizeof(array)
的静态断言来验证我的期望。但是我在其他转换单元中使用array
,所以我必须用显式大小声明它。所以这个把戏对我不起作用。我有个主意
#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]
#define NUM_ARGS__(X, \
N64,N63,N62,N61,N60, \
N59,N58,N57,N56,N55,N54,N53,N52,N51,N50, \
N49,N48,N47,N46,N45,N44,N43,N42,N41,N40, \
N39,N38,N37,N36,N35,N34,N33,N32,N31,N30, \
N29,N28,N27,N26,N25,N24,N23,N22,N21,N20, \
N19,N18,N17,N16,N15,N14,N13,N12,N11,N10, \
N09,N08,N07,N06,N05,N04,N03,N02,N01, N, ...) N
#define NUM_ARGS(...) \
NUM_ARGS__(0, __VA_ARGS__, \
64,63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define DECL_INIT_ARRAYN(TYPE, NAME, COUNT, N, ...) \
C_ASSERT(COUNT == N); \
TYPE NAME[COUNT] = { __VA_ARGS__ }
#define DECL_INIT_ARRAY(TYPE, NAME, COUNT, ...) \
DECL_INIT_ARRAYN(TYPE, NAME, COUNT, NUM_ARGS(__VA_ARGS__), __VA_ARGS__)
DECL_INIT_ARRAY(const int, array3_3, 3, 1, 2, 3);
int main(void)
{
DECL_INIT_ARRAY(const int, array5_4, 5, 1, 2, 3, 4);
DECL_INIT_ARRAY(const int, array5_6, 5, 1, 2, 3, 4, 5, 6);
return 0;
}
输出():
UPD:OP基于使用\uu VA\u ARGS\uuu
和静态/编译时断言的相同思想,找到了一个较短的C++11解决方案:
#include <tuple>
#define DECL_INIT_ARRAY(TYPE, NAME, COUNT, ...) \
static_assert(COUNT == \
std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value, \
"Array " #NAME " should have exactly " #COUNT " initializers"); \
TYPE NAME[COUNT] = { __VA_ARGS__ }
DECL_INIT_ARRAY(const int, array3_3, 3, 1, 2, 3);
int main(void)
{
DECL_INIT_ARRAY(const int, array5_4, 5, 1, 2, 3, 4);
DECL_INIT_ARRAY(const int, array5_6, 5, 1, 2, 3, 4, 5, 6);
return 0;
}
由于您正在其他翻译单元中使用数组
,因此它显然具有外部链接。在这种情况下,您可以声明它两次,只要声明给它相同的类型。所以只需声明两次,一次允许编译器计算初始值设定项,一次指定大小。将此行放在一个源文件中,在声明数组的任何头之前:
int array[] = { 1, 2, 3 };
稍后在同一文件中,放置一行声明数组的#include
行,其中一行如下:
extern int array[10];
如果两个声明中的数组大小不同,编译器将报告错误。如果它们相同,编译器将接受它们。(根据请求从注释中升级)
如果数组中的值对于系统的正确功能非常重要,并且在最后使用零初始化值会导致错误,那么我只需添加一个单元测试来验证数组是否包含正确的数据,我没有在代码中强制执行它。我在C99中查找了这个问题的具体答案,在这里找到了答案:
如果未定义数组的大小并使用:
int test[] = {1,2}
STATIC_ASSERT(sizeof(test)/sizeof(test[0]) == 3)
/* with C11 support: */
_Static_assert(sizeof(test)/sizeof(test[0]) == 3)
/* or more easily */
ASSERT_ARRAY_LENGTH(test, 3);
您可以轻松地检测阵列的大小是否如预期的那样。如果静态断言失败,将引发编译错误。没有运行时开销。静态断言的一个非常可靠的实现可以在这里找到:
为方便起见:
#define ASSERT_CONCAT_(a, b) a##b
#define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
/* These can't be used after statements in c89. */
#ifdef __COUNTER__
#define STATIC_ASSERT(e,m) \
;enum { ASSERT_CONCAT(static_assert_, __COUNTER__) = 1/(int)(!!(e)) }
#else
/* This can't be used twice on the same line so ensure if using in headers
* that the headers are not included twice (by wrapping in #ifndef...#endif)
* Note it doesn't cause an issue when used on same line of separate modules
* compiled with gcc -combine -fwhole-program. */
#define STATIC_ASSERT(e,m) \
;enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(int)(!! (e)) }
#endif
我在上面添加了一个宏,专门用于验证数组的大小。元素的数量必须与指定的长度完全匹配:
#define ASSERT_ARRAY_LENGTH(array, length)\
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == length,\
"Array is not of expected length")
如果您不需要支持C99,您可以使用新的C11特性\u Static\u assert。更多信息。
如果不需要C支持,也可以依赖C++静态断言:
std::size(test) == 3; /* C++ 17 */
(std::end(test) - std::begin(end)) == 3; /* C++ 14 */
我不确定是否有警告,但任何为此给出错误的编译器都是不符合要求的编译器。我无法想象编译器供应商会在他们的产品中添加这样的选项,这就是静态分析工具的用途。GCC有-Wmissing字段初始值设定项
,但它只适用于其他聚合,而不适用于数组,可能是因为大多数人不希望它对数组发出警告。难道你不能使用单元测试来确保数组包含正确的值并且尾部元素没有初始化为零吗?@JonathanWakely反过来,std::array
是一个聚合!(事实上,我不喜欢这个警告。)@Luc Danton尽管如此,我相信C数组的许多运算问题都会随着std::array
而消失。你现在甚至可以用{}
初始化,对吗?@LucDanton,这是一个不同的警告,-Wmissing-braces
,默认情况下GCC4.8没有启用它,因为std::array
在我的例子中,COUNT
是几千,所以计算宏会很长。但也许有一种C++11方法可以让它工作?类似于std::tuple\u size::value或类似的东西。是的,我的建议。你想更新你的答案,我可以编辑它吗,还是我应该把它作为一个单独的答案发布?酷!我将把你的部分加入到答案中。我的想法是一样的,例如声明一个临时数组并检查它的大小,但我意识到如果这个数组是全局的,它可能不会被优化。模板魔术和新的C++特性似乎有很大帮助。这些经常被忽视的特征之一。在C和C++中,这是相同的吗?C++将两行定义为定义,但在第二行的开始插入 ExtNe>代码,使它成为一个不是定义的声明。很好,我尝试过,但是我把这两个声明反过来,这不给出诊断(因为我现在想清楚的原因)。这种方法的问题是,定义数组的源文件包含带有导出增量的头文件。因此,在处理int-array[]
时,编译器已经看到了extern-int-array[10]
,因此将再次默认10为大小。您可以在声明之前将定义包含在标头中,但由宏(如DEFINE\u-array
)保护,该宏仅由源文件设置。。虽然我同意单元测试是个好主意,但你似乎暗示静态代码检查不会增加价值。我相信添加静态断言比单元测试有一些好处(但不必取代单元测试):-当代码被修改或读取时,编写代码时做出的假设是明确的(契约式设计)-静态断言是编译器进行单元测试的一种形式,它们会自动运行,因此是一种很好的持续集成和测试形式。不,我不是这么说的。“我将使用测试来验证这个特定的约束”并不意味着“静态断言是无用的,不要麻烦使用它们”。为什么您更喜欢单元测试而不是试图在代码中强制执行它
#define ASSERT_ARRAY_LENGTH(array, length)\
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == length,\
"Array is not of expected length")
std::size(test) == 3; /* C++ 17 */
(std::end(test) - std::begin(end)) == 3; /* C++ 14 */