Определение макроса ARRAY_SIZE - PullRequest
13 голосов
/ 05 ноября 2011

Я обнаружил следующее определение макроса при чтении globals.h в проекте Google V8 .

// The expression ARRAY_SIZE(a) is a compile-time constant of type
// size_t which represents the number of elements of the given
// array. You should only use ARRAY_SIZE on statically allocated
// arrays.

#define ARRAY_SIZE(a)                               \
  ((sizeof(a) / sizeof(*(a))) /                     \
  static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))

Мой вопрос - последняя часть: static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))). На мой взгляд, одна вещь заключается в следующем: поскольку последняя часть всегда будет иметь значение 1, которое имеет тип size_t, все выражение будет преобразовано в size_t.

Если это предположение верно, то возникает другой вопрос: поскольку тип возвращаемого значения оператора sizeof равен size_t, почему такое продвижение необходимо? Какая польза от определения макроса таким образом?

Ответы [ 5 ]

11 голосов
/ 05 ноября 2011

Как объяснено, это слабая (*) попытка обезопасить макрос от использования с указателями (а не с истинными массивами), где он не сможет правильно оценить размер массива. Это, конечно, связано с тем, что макросы являются чисто текстовыми манипуляциями и не имеют понятия AST .

Поскольку вопрос также помечен C ++, я хотел бы отметить, что C ++ предлагает безопасную альтернативу типа: шаблоны.

#ifdef __cplusplus
   template <size_t N> struct ArraySizeHelper { char _[N]; };

   template <typename T, size_t N>
   ArraySizeHelper<N> makeArraySizeHelper(T(&)[N]);

#  define ARRAY_SIZE(a)  sizeof(makeArraySizeHelper(a))
#else
#  // C definition as shown in Google's code
#endif

В качестве альтернативы, скоро можно будет использовать constexpr:

template <typename T, size_t N>
constexpr size_t size(T (&)[N]) { return N; }

Однако мой любимый компилятор (Clang) все еще не реализует их: x

В обоих случаях, поскольку функция не принимает параметры указателя, вы получите ошибку во время компиляции, если тип неправильный.

(*) слабый в том смысле, что он не работает для небольших объектов, где размер объектов является делителем размера указателя.


Просто демонстрация того, что это значение времени компиляции:

template <size_t N> void print() { std::cout << N << "\n"; }

int main() {
  int a[5];
  print<ARRAY_SIZE(a)>();
}

Посмотрите в действии на IDEONE .

10 голосов
/ 05 ноября 2011

последняя часть всегда будет иметь значение 1, которое имеет тип size_t,

В идеале, более поздняя часть оценивается как bool (т.е. true / false), а с использованием static_cast<> она преобразуется в size_t.

зачем нужна такая раскрутка? В чем выгода определения макрос таким образом?

Я не знаю, является ли это идеальным способом определения макроса. Однако, одно вдохновение я нахожу в комментариях: //You should only use ARRAY_SIZE on statically allocated arrays.

Предположим, что если кто-то пропустит указатель , то произойдет сбой для типов данных struct (если он больше размера указателя).

struct S { int i,j,k,l };
S *p = new S[10];
ARRAY_SIZE(p); // compile time failure !

[Примечание: эта техника может не показывать ошибки для int*, char*, как сказано.]

8 голосов
/ 05 ноября 2011

Если у sizeof(a) / sizeof(*a) есть некоторый остаток (т. Е. a не является целым числом *a), тогда выражение оценивается как 0, а компилятор выдаст вам деление на ноль ошибка во время компиляции .

Я могу только предположить, что автор макроса был сожжен в прошлом чем-то, что не прошло этот тест.

5 голосов
/ 04 декабря 2013

В ядре Linux макрос определяется как (специфично для GCC):

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

, где __must_be_array() равно

/* &a[0] degrades to a pointer: a different type from an array */
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

и __same_type() равно

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
2 голосов
/ 05 ноября 2011

Вторая часть хочет убедиться, что sizeof( a ) делится на sizeof( *a ).

Таким образом, (sizeof(a) % sizeof(*(a))) часть. Если это делимое, выражение будет оценено как 0. Здесь идет часть ! - !(0) даст true. Вот почему актерский состав необходим. На самом деле, это не влияет на вычисление размера, просто добавляет проверку времени компиляции.

Поскольку это время компиляции, в случае, если (sizeof(a) % sizeof(*(a))) не 0, у вас будет ошибка времени компиляции для 0 деления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...