Каждый программист C может определять количество элементов в массиве с помощью этого известного макроса:
#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a])
Вот типичный прецедент:
int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19};
printf("%lu\n", NUM_ELEMS(numbers)); // 8, as expected
Однако ничто не мешает программисту случайно передать указатель вместо массива:
int * pointer = numbers;
printf("%lu\n", NUM_ELEMS(pointer));
В моей системе это печатает 2, потому что, по-видимому, указатель в два раза больше целого. Я подумал о том, как не дать программисту пропустить указатель по ошибке, и я нашел решение:
#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a]))
Это работает, потому что указатель на массив имеет то же значение, что и указатель на его первый элемент. Если вы передадите указатель вместо этого, указатель будет сравниваться с указателем на себя, который почти всегда является ложным. (Единственное исключение - это рекурсивный указатель на пустоту, то есть указатель на пустоту, указывающий на себя. Я могу жить с этим.)
Случайное прохождение указателя вместо массива теперь вызывает ошибку во время выполнения:
Assertion `(void*)&(pointer) == (void*)(pointer)' failed.
Ницца! Теперь у меня есть пара вопросов:
-
Я использую
assert
как левый операнд выражения для запятой, допустимый стандарт C? То есть стандарт позволяет мне использоватьassert
в качестве выражения? Извините, если это глупый вопрос:) -
Можно ли как-то выполнить проверку во время компиляции?
-
Мой компилятор C считает, что
int b[NUM_ELEMS(a)];
является VLA. Любой способ убедить его в противном случае? -
Я первый, кто подумал об этом? Если да, то сколько девственников я могу ожидать, ожидая меня на небесах?:)