Initializing an array (in C++, but any solution which works for C will likely work here as well) with less initializers than it has elements is perfectly legal:
int array[10] = { 1, 2, 3 };
However, this can be a source of obscure bugs. Is there a way to have the compiler (gcc) check the number of initializers for one specific array, and emit a warning or even an error if declared and actual size don't match?
I know I can use int array[] = { 1, 2, 3 };
and could then use static assertions involving sizeof(array)
to verify my expectation there. But I'm using array
in other translation units, so I have to declare it with an explicit size. So this trick won't work for me.
I have an idea.
#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;
}
Output (ideone):
prog.c: In function ‘main’:
prog.c:33:3: error: size of array ‘CAssertExtern’ is negative
prog.c:34:3: error: size of array ‘CAssertExtern’ is negative
prog.c:34:3: error: excess elements in array initializer [-Werror]
prog.c:34:3: error: (near initialization for ‘array5_6’) [-Werror]
prog.c:34:3: error: unused variable ‘array5_6’ [-Werror=unused-variable]
prog.c:33:3: error: unused variable ‘array5_4’ [-Werror=unused-variable]
prog.c:34:3: error: unused variable ‘CAssertExtern’ [-Werror=unused-variable]
cc1: all warnings being treated as errors
UPD: The OP has found a shorter C++11 solution, building upon the same idea of using __VA_ARGS__
and a static/compile-time assertion:
#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;
}
Output (ideone):
prog.cpp: In function ‘int main()’:
prog.cpp:13:3: error: static assertion failed: Array array5_4 should have exactly 5 initializers
prog.cpp:14:3: error: static assertion failed: Array array5_6 should have exactly 5 initializers
prog.cpp:14:3: error: too many initializers for ‘const int [5]’
prog.cpp:13:3: warning: unused variable ‘array5_4’ [-Wunused-variable]
prog.cpp:14:3: warning: unused variable ‘array5_6’ [-Wunused-variable]
Since you are using array
in other translation units, it apparently has external linkage. In this case, you are allowed to declare it twice, as long as the declarations give it the same type. So simply declare it twice, once allowing the compiler to count the initializers and once specifying the size. Put this line in one source file, before any header that declares array
:
int array[] = { 1, 2, 3 };
Later in the same file, put an #include
line that declares array
, with a line such as:
extern int array[10];
If the array sizes differ in the two declarations, the compiler will report an error. If they are the same, the compiler will accept them.
I looked around for a specific answer to this in C99 and found an answer here: How can I use “sizeof” in a preprocessor macro?
If you don't define the size of your array and use:
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);
you can easily detect if the sizeof the array is as you expected. A compilation error will be raised if the static assert fails. There is no run time overhead. A very solid implementation of the static assert can be found here:
Static assert implementation C
for your convenience:
#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
I Added a macro on top of this one specifically for validating the sizeof an array. The number of elements must exactly match the specified length:
#define ASSERT_ARRAY_LENGTH(array, length)\
STATIC_ASSERT(sizeof(array)/sizeof(array[0]) == length,\
"Array is not of expected length")
If you don't need to support C99 you can use the new C11 feature _Static_assert. More info here.
If you don't require C support, you can also rely on the c++ static assert:
std::size(test) == 3; /* C++ 17 */
(std::end(test) - std::begin(end)) == 3; /* C++ 14 */