Пусть компилятор проверит количество инициализаторов массива

Инициализация массива (в C ++, но любое решение, которое работает для C, вероятно, также будет работать и здесь) с меньшим количеством инициализаторов, чем с элементами, вполне законна:

int array[10] = { 1, 2, 3 };

Однако это может быть источником неясных ошибок. Есть ли способ заставить компилятор (gcc) проверять количество инициализаторов для одного конкретного массива и выдавать предупреждение или даже ошибку, если объявлено и фактический размер не совпадает?

Я знаю, что могу использовать int array[] = { 1, 2, 3 }; и затем может использовать статические утверждения, включающие sizeof(array) чтобы проверить мои ожидания там. Но я пользуюсь array в других единицах перевода, поэтому я должен объявить это с явным размером. Так что этот трюк не сработает для меня.

15

Решение

(повышен из комментария по запросу)

Если значения в массиве важны для правильной функциональности системы, а нулевые инициализированные значения в конце приводят к ошибкам, то я бы просто добавил модульный тест, чтобы убедиться, что массив содержит правильные данные, вместо того, чтобы пытаться применить это в коде.

6

Другие решения

У меня есть идея.

#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 NAME1 = { __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;
}

Выход (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: OP нашел более короткое решение C ++ 11, основанное на той же идее использования __VA_ARGS__ и статическое утверждение во время компиляции:

#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 NAME2 = { __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;
}

Выход (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]
4

Так как вы используете array в других переводческих единицах он, очевидно, имеет внешнюю связь. В этом случае вам разрешается объявлять его дважды, если в объявлениях указан один и тот же тип. Так что просто объявите это дважды, один раз позволив компилятору посчитать инициализаторы и один раз указав размер. Поместите эту строку в один исходный файл, перед любым заголовком, который объявляет array:

int array[] = { 1, 2, 3 };

Позже в том же файле положить #include линия, которая объявляет arrayсо строкой, такой как:

extern int array[10];

Если размеры двух массивов отличаются в двух объявлениях, компилятор сообщит об ошибке. Если они одинаковы, компилятор примет их.

4

Я искал конкретный ответ на этот вопрос в C99 и нашел ответ здесь: Как я могу использовать «sizeof» в макросе препроцессора?

Если вы не определяете размер вашего массива и используете:

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);

Вы можете легко определить, соответствует ли размер массива ожидаемому. Ошибка компиляции будет возникать в случае сбоя статического утверждения. Там нет времени выполнения накладных расходов. Очень надежная реализация статического утверждения может быть найдена здесь:
Реализация статического утверждения C

для вашего удобства:

#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 _Static_assert. Больше информации Вот.
Если вам не требуется поддержка C, вы также можете положиться на статическое утверждение c ++:

std::size(test) == 3; /* C++ 17 */
(std::end(test) - std::begin(end)) == 3; /* C++ 14 */
3
По вопросам рекламы [email protected]