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(), которая включает в себя несколько морщин, которые здесь не рассматриваются.

Я не могу улучшить источник, который имеет четкие и полные комментарии.