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