Подтвердить что ты не робот

Определение макроса ARRAY_SIZE

При чтении 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, почему такое продвижение необходимо? Какая польза от определения макроса таким образом?

Большое спасибо за чтение.

4b9b3361

Ответ 1

Последняя часть всегда будет оценивать до 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* как сказано.]

Ответ 2

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

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

#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 code
#endif

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

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

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

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

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


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

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

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

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

Ответ 3

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

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

Ответ 4

В ядре 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))

Ответ 5

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

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

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