ARRAYSIZE С++ macro: как это работает?
Хорошо, я не совсем новичок, но я не могу сказать, что понимаю следующий макрос. Самая запутанная часть - это разделение со значением, отличным от size_t: что же это делает? Тем более, что я вижу оператор отрицания, который, насколько я знаю, может привести к нулевому значению. Разве это не означает, что это может привести к ошибке деления на нуль? (Кстати, макрос правилен и прекрасно работает.)
#define ARRAYSIZE(a) \
((sizeof(a) / sizeof(*(a))) / \
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
Ответы
Ответ 1
Первая часть (sizeof(a) / sizeof(*(a)))
довольно проста; он делит размер всего массива (предполагая, что вы передаете макрокоманду объект типа массива, а не указатель) на размер первого элемента. Это дает количество элементов в массиве.
Вторая часть не так проста. Я думаю, что потенциал деления на ноль преднамерен; это приведет к ошибке времени компиляции, если по какой-либо причине размер массива не является целым числом, кратным одному из его элементов. Другими словами, это какая-то проверка работоспособности во время компиляции.
Однако, я не вижу, при каких обстоятельствах это может произойти... Как люди предложили в комментариях ниже, он поймает некоторое злоупотребление (например, используя ARRAYSIZE()
на указателе). Однако он не поймает все такие ошибки.
Ответ 2
Деление в конце кажется попыткой обнаружить аргумент без массива (например, указатель).
Не удается обнаружить это, например, для char*
, но будет работать для T*
, где sizeof(T)
больше, чем размер указателя.
В С++ обычно предпочитается следующий шаблон функции:
typedef ptrdiff_t Size;
template< class Type, Size n >
Size countOf( Type (&)[n] ) { return n; }
Этот шаблон функции не может быть создан с аргументом указателя, только с массивом. В С++ 11 он может альтернативно быть выражен в терминах std::begin
и std::end
, который автоматически позволяет ему работать и для стандартных контейнеров с итераторами произвольного доступа.
Ограничения: не работает для массива локального типа в С++ 03 и не дает размер времени компиляции.
Для размера времени компиляции вы можете вместо этого сделать
template< Size n > struct Sizer { char elems[n]; };
template< class Type, size n >
Sizer<n> countOf_( Type (&)[n] );
#define COUNT_OF( a ) sizeof( countOf_( a ).elems )
Отказ от ответственности: весь код не тронут руками компилятора.
Но, вообще говоря, просто используйте первый шаблон функции, countOf
.
Приветствия и hth.
Ответ 3
предположим, что
T arr[42];
ARRAYSIZE(arr)
будет расширяться до (грубо)
sizeof (arr) / sizeof(*arr) / !(sizeof(arr) % sizeof(*arr))
который в этом случае дает 42/! 0, что составляет 42
Если по какой-то причине sizeof массив не делится на sizeof его элемента, произойдет деление на ноль. Когда это может произойти? Например, когда вы передаете динамически выделенный массив вместо статического!
Ответ 4
Это приводит к ошибке деления на нуль (намеренно). Способ работы этого макроса заключается в том, что он делит размер массива в байтах на размер одного элемента массива в байтах. Поэтому, если у вас есть массив значений int
, где int
- 4 байта (на большинстве 32-разрядных машин), массив из 4 int
значений будет равен 16 байтам.
Поэтому, когда вы вызываете этот макрос в таком массиве, он делает sizeof(array) / sizeof(*array)
. А так как 16/4 = 4, он возвращает, что в массиве есть 4 элемента.
Примечание: *array
разыгрывает первый элемент массива и эквивалентен array[0]
.
Второе деление делает модульное деление (получает остаток от деления), и поскольку любое ненулевое значение считается "истинным", использование оператора !
приведет к делению на ноль, если остаток деления отлична от нуля (и аналогично, деление на 1 в противном случае).
Ответ 5
Я написал эту версию этого макроса. Рассмотрим более старую версию:
#include <sys/stat.h>
#define ARRAYSIZE(a) (sizeof(a) / sizeof(*(a)))
int main(int argc, char *argv[]) {
struct stat stats[32];
std::cout << "sizeof stats = " << (sizeof stats) << "\n";
std::cout << "sizeof *stats = " << (sizeof *stats) << "\n";
std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n";
foo(stats);
}
void foo(struct stat stats[32]) {
std::cout << "sizeof stats = " << (sizeof stats) << "\n";
std::cout << "sizeof *stats = " << (sizeof *stats) << "\n";
std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n";
}
На 64-битной машине этот код производит этот вывод:
sizeof stats = 4608
sizeof *stats = 144
ARRAYSIZE=32
sizeof stats = 8
sizeof *stats = 144
ARRAYSIZE=0
Что происходит? Как ARRAYSIZE переместился с 32 на ноль? Ну, проблема в том, что параметр функции на самом деле является указателем, хотя он похож на массив. Таким образом, внутри foo "sizeof (stats)" равно 8 байтам, а "sizeof (* stats)" все равно 144.
С новым макросом:
#define ARRAYSIZE(a) \
((sizeof(a) / sizeof(*(a))) / \
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
Если sizeof (a) не кратно sizeof (* (a)), то% не равно нулю, что! инвертирует, а затем static_cast оценивает до нуля, вызывая разделение времени компиляции на ноль. Таким образом, насколько это возможно в макросе, это странное деление улавливает проблему во время компиляции.
PS: в С++ 17 просто используйте std:: size, см. http://en.cppreference.com/w/cpp/iterator/size
Ответ 6
Де-ноль может пытаться поймать ошибки выравнивания, вызванные любой причиной. Как если бы с некоторыми настройками компилятора размер элемента массива был 3, но компилятор бы обходил его до 4 для более быстрого доступа к массиву, тогда массив из 4 записей имел бы размер 16 и! (16/3) к нулю, давая деление на ноль во время компиляции. Тем не менее, я не знаю, какой компилятор делает подобное, и может быть против спецификации С++ для sizeof вернуть размер, который отличается от размера этого типа в массиве.
Ответ 7
Поздняя вечеринка здесь...
В кодовой базе Google С++ есть ИМХО окончательная реализация С++ макроса arraysize()
, которая включает в себя несколько морщин, которые здесь не рассматриваются.
Я не могу улучшить источник, который имеет четкие и полные комментарии.