Question

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.

Was it helpful?

Solution

(promoted from a comment as requested)

If the values in the array are important to the correct functionality of the system, and having zero-initialized values at the end causes bugs, then I would just add a unit test to verify the array contains the right data, instead of trying to enforce it in the code.

OTHER TIPS

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 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]

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 */
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top