C найти размер статического массива (предотвращение ошибок)
Поиск размера статического массива - обычная операция. см. C найдите размер статического массива - sizeof(a) / sizeof((a)[0])
Это может быть завернуто в макрос, например:
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
однако его можно случайно передать обычным указателем.
например: void func(SomeArray **foo) { int i = ARRAY_SIZE(foo); }
Пока он действительный C, но часто заканчивается логической ошибкой.
Возможно предотвратить эту ошибку (используя преимущество каждого процессора для отказа в битовом поле нулевой длины).
#define ARRAY_SIZE(a) \
((sizeof(struct { int isnt_array : \
((const void *)&(a) == &(a)[0]); }) * 0) + \
(sizeof(a) / sizeof(*(a))))
Я нашел, что этот макрос работает с GCC, но не работает с Clang, для косвенно связанных членов. с error: expression is not an integer constant expression
например:
-
char word[8]; int i = ARRAY_SIZE(word);
ok.
-
struct Bar { word[8]; }
void func(struct Bar *foo) { int i = ARRAY_SIZE(foo->word); }
не работает.
Есть ли более переносимый способ реализовать это? (работа с Clang хороша, конечно, хотя я заинтересован в общей переносимости... других компиляторов тоже).
Это кажется такой общей задачей, что было бы неплохо иметь повторно используемый портативный макрос.
Ответы
Ответ 1
Попробуйте следующее:
#define ASSERT_ARRAY(a) \
sizeof(char[1-2*__builtin_types_compatible_p(__typeof__(a), __typeof__(&(a)[0]))])
#define ARRAY_SIZE(a) \
(ASSERT_ARRAY(a)*0 + sizeof(a)/sizeof((a)[0]))
Он не переносимый, но работает как с gcc
, так и clang
и имеет меньше побочных эффектов, чем n.m. предложение.
Ответ 2
Этот макрос работает (в моих тестах так или иначе) для clang и gcc. Я почти уверен, что нет портативного решения.
#define ARRAY_SIZE(a) \
(({ static __typeof__(a) _aa; \
static __typeof__(&(a)[0]) _pa = _aa; (void)_pa; }), \
sizeof(a)/sizeof((a)[0]))
Ответ 3
Это то, что я использую, с решением для C и С++.
Для меня требовалось, чтобы оба работали даже на VLA.
#ifndef __cplusplus
int _ptr_used_(void) __attribute__((error("Pointer used in place of array") ));
#define ARRAY_SIZEOF(arr) ( \
__builtin_types_compatible_p(typeof(arr), typeof((arr)[0])*) \
? _ptr_used_() \
: sizeof(arr)/sizeof((arr)[0]) \
)
#else
/// A type that exists
struct _true_ {};
template <bool constant>
struct is_an_array
{
/// Used when a constant sized (non-VLA) object is passed in
/// only allow arrays past
template<class B, size_t n>
static _true_ test( B(&)[n] );
};
template <>
struct is_an_array<false>
{
/// This happens only for VLAs; force decay to a pointer to let it work with templates
template <class B>
static _true_ test(B *n);
};
# define ARRAY_SIZEOF(arr) ({ typedef decltype(is_an_array<static_cast<bool>(__builtin_constant_p(sizeof(arr)))>::test(arr)) type; sizeof(arr) / sizeof((arr)[0]); })
#endif
Ответ 4
Вам действительно нужно утверждение времени компиляции? Если да, я боюсь, что нет портативного способа, вы можете заставить это работать только на Clang и GCC, или на какой-то другой компилятор, с трюками, специфичными для реализации.
Но если вы решите перейти на переносимость, вы можете использовать ошибку времени выполнения (что может быть столь же эффективным, в зависимости от вашей стратегии тестирования). Предположим, что у вас есть функция отчетов об ошибках void error(char *errorText)
. Макрос мог бы выглядеть примерно так (непроверенный, но я надеюсь, что вы поняли):
#ifdef DEBUG /* Place your debug-mode-flag macro here.
You won't want the extra branch in the release build */
#define ARRAY_SIZE(a) \
((const void *)&(a) == &(a)[0]) ? \
(sizeof(a) / sizeof(*(a))) : (error("Tried to treat pointer as array!"), 0)
#else
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
#endif