Проверка аргумента - это тип ARRAY в макросе предварительной обработки c/С++ во время компиляции

Есть ли способ проверить во время компиляции макроса c, что аргумент является массивом?

например, в этих двух макросах:

#define CLEAN_ARRAY(arr) \
    do { \
        bzero(arr, sizeof(arr)); \
    } while (0)

и

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

Я попробовал что-то, используя макрос CTC(X), но не смог найти способ проверить/предупредить, если arr не является массивом.

Ответ 1

Здесь решение в чистом C, которое вызывает поведение undefined:

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))

Если вам нужно убедиться, что это значение является массивом (тогда возникает ошибка времени компиляции, если нет), вы можете просто использовать его как инициализатор для оператора перечисления (или статической переменной), например:

static int __ ## arg ## _is_array = IS_ARRAY(arg); // works for an array, fails for pointer.

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


Старые ответы:

Так как это помечено как C (и GCC), я попробую решение здесь:

#define IS_ARRAY(arg) __builtin_choose_expr(__builtin_types_compatible_p(typeof(arg[0]) [], typeof(arg)), 1, 0)

Другое решение с использованием функции C11 _Generic и typeof:

#define IS_ARRAY(arg) _Generic((arg),\
    typeof(arg[0]) *: 0,\
    typeof(arg[0]) [sizeof(arg) / sizeof(arg[0])]: 1\
)

В принципе, все, что он делает, это использование некоторых причудливых функций GCC, чтобы определить, совместим ли тип аргумента с массивом типа элементов аргумента. Он вернет 0 или 1, и вы можете заменить 0 на то, что создает ошибку времени компиляции, если вы хотите.

Ответ 2

Чистое решение C99:

enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) };
typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1];

Это использует тот факт, что адрес массива совпадает с адресом его первого члена и что элемент перечисления должен быть интегральной константой. Если компилятор достаточно умен, чтобы сказать, что указатель на указатель имеет отдельный адрес, он будет вторгаться во второй оператор.

Нам все еще нужен первый оператор, потому что иначе компилятор с поддержкой массивов времени исполнения (например, gcc 4.7) будет выполнять сравнение адресов во время выполнения и вызывать поведение undefined, поскольку размер массива времени выполнения отрицательный (например, при gcc программа segfaults).

Полная программа:

#include <strings.h>

#define CLEAN_ARRAY(arr) \
    do { \
        enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) }; \
        typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1]; \
        bzero(arr, sizeof(arr)); \
    } while (0)

int main() {
    int arr[5];
    CLEAN_ARRAY(arr);
    int *ptr;
    CLEAN_ARRAY(ptr);  // error: enumerator value for ‘must_be_an_array’ is not an integer constant
    return 0;
}

Ответ 3

Как проверить в макросе c аргумент имеет тип ARRAY

Используйте std::is_array внутри макроса. Или забудьте themacro и просто используйте std::is_array.

Относительно ARRAY_SIZE,

constexpr size_t size(T const (&)[N])
{
  return N;
}

Ответ 4

Согласно моему комментарию:

sizeof((x)[0]) выдаст ошибку, если тип не является "индексируемым" типом - однако, если это будет указатель, он с радостью примет это. А также, если существует operator[] для типа x.

В C это довольно сложно сделать, но С++ может допускать некоторые решения типа шаблона (я действительно не знаю, как это сделать, поскольку я никогда не пытался это сделать или что-либо подобное с шаблонами).

Ответ 5

Насколько я могу судить, никто еще не предоставил способ убедиться, что аргумент ARRAY_SIZE на самом деле является массивом.

& Lt; Правк
Найдено макрос размера массива, который отклоняет указатели
Исходный ответ:
& Lt;/Правк

Макрос, который я использую для этого:

#define ASSERT_EXPR(condition,return_value) \
(((char(*)[(condition)?1:-1])0)?(return_value):(return_value))

Принцип:
0 приводится к указателю на массив (с размером единицы (условие true) или минус один (условие false, ошибка генерации)). Этот нулевой указатель затем используется как условие троичного оператора. Хотя мы знаем, что он всегда будет вычислять только третий операнд (нулевой указатель означает ложь), второй операнд также является return_value - таким образом, результирующий тип совпадает с типом return_value.

Используя это (и IS_ARRAY из ответа Ричарда Дж. Росса III), я могу определить свой безопасный макрос ARRAY_SIZE следующим образом:

#define IS_ARRAY(arg) __builtin_choose_expr(__builtin_types_compatible_p(typeof(arg[0]) [], typeof(arg)), 1, 0)
#define ARRAY_SIZE(x) ASSERT_EXPR(IS_ARRAY(x), (sizeof(x)/sizeof((x)[0])) )

Мне не удалось заставить его работать с Ричардом Дж. Россом III двумя другими вариантами IS_ARRAY, но это может быть моей (или gcc) ошибкой...

Ответ 6

(для С++) мой текущий прозрачный макрос в VS2010:

#define CLEAR(v)    do { __pragma(warning(suppress: 4127 4836)) typedef std::remove_reference< decltype(v)>::type T; static_assert( std::is_pod<T>::value || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), "must not CLEAR a non-POD!" ); static_assert( !std::is_pointer<T>::value, "pointer passed to CLEAR!" );  memset(&(v), 0, sizeof(v)); } while(0)

Вы можете составить свой вариант, используя материал в заголовке type_traits.

Отформатированная версия с пояснениями:

#define CLEAR(v) \
do { \ 
    __pragma(warning(suppress: 4127 4836)) \
    typedef std::remove_reference< decltype(v)>::type T; \
    static_assert( \
        std::is_pod<T>::value \
        || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), \
        "must not CLEAR a non-POD!" ); \
     static_assert( !std::is_pointer<T>::value, "pointer passed to CLEAR!" ); \
     memset(&(v), 0, sizeof(v)); \
  } while(0)

external do-while, чтобы сделать его пригодным для использования, как подлинную функцию во всех местах, включая if/else.

Remove_reference требуется, чтобы он работал с lvalues, только decltype делает int * и int * & другой и is_pointer сообщает false для последнего.

Проверка is_pod хороша для общего, дополнительное условие позволяет struct A1: A; где A является POD и A1 добавляет только больше членов POD. Для цели is_pod это ложь, но очистить ее делает тот же смысл.

Проверка

is_pointer защищает ожидаемый тупик, когда вы неправильно указываете направление указателя или передаете адрес структуры в замешательстве. Используйте = NULL, чтобы очистить указатель.; -)

__pragma существует для подавления предупреждений L4, которые выдаются иначе.

Ответ 7

В C это должно работать:

#define VALIDATE_ARRAY(arr) (void)(sizeof((arr)[0]))

int *a, b;
int main() {
        VALIDATE_ARRAY(a);
        VALIDATE_ARRAY(b);
        return 0;
}

Эта программа не скомпилируется, потому что b не является массивом. Это связано с тем, что b[0] является недопустимым. Он не будет отличать указатели от массивов - я не думаю, что вы можете это сделать.

Эта форма макроса может использоваться только внутри функции. Если вы хотите использовать его вне функции, вам придется изменить его (например, объявить массив extern).