Массив размера массива, который отклоняет указатели

Обычно широко используемый макрос размера массива

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0])) 

или некоторое эквивалентное образование. Тем не менее, такого рода вещи бесшумно преуспевают, когда передается указатель, и дает результаты, которые могут показаться правдоподобными во время выполнения, пока вещи не таинственно разваливаются.

Слишком легко сделать эту ошибку: функция, которая имеет переменную локального массива, реорганизуется, перемещая немного манипуляции с массивом в новую функцию, называемую массивом в качестве параметра.

Итак, возникает вопрос: существует ли «санитарный» макрос для обнаружения неправильного использования макроса ARRAYSIZE в C, желательно во время компиляции? В C ++ мы просто использовали шаблон, специализированный только для аргументов массива; в C, кажется, нам нужно каким-то образом различать массивы и указатели. (Если бы я хотел отклонить массивы, например, я бы просто сделал eg (arr=arr, ...) потому что назначение массива является незаконным).

Ядро Linux использует хорошую реализацию ARRAY_SIZE для решения этой проблемы:

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

с

 #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0])) 

а также

 #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) 

Разумеется, это переносимо только в GNU C, так как оно использует два instrinsics: typeof operator и __builtin_types_compatible_p . Также он использует свой «знаменитый» макрос BUILD_BUG_ON_ZERO который действителен только в GNU C.

Предполагая требование оценки времени компиляции (это то, что мы хотим), я не знаю никакой переносимой реализации этого макроса.

«Полупрозрачная» реализация (и которая не распространяется на все случаи):

 #define ARRAY_SIZE(arr) \ (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr))) 

с

 #define IS_ARRAY(arr) ((void*)&(arr) == &(arr)[0]) #define STATIC_EXP(e) \ (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);})) 

С gcc это не дает никаких предупреждений, если аргумент представляет собой массив в -std=c99 -Wall но -pedantic дает предупреждение. Причина в том, IS_ARRAY выражение IS_ARRAY не является целочисленным константным выражением (приведение типов указателей и оператор IS_ARRAY индекса не допускается в целочисленных константных выражениях), а ширина битового поля в STATIC_EXP требует целочисленного постоянного выражения.

Эта версия ARRAYSIZE() возвращает 0 когда arr является указателем и размером, когда его чистый массив

 #include  #define IS_INDEXABLE(arg) (sizeof(arg[0])) #define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg))) #define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0) int main(void) { int a[5]; int *b = a; int n = 10; int c[n]; /* a VLA */ printf("%zu\n", ARRAYSIZE(a)); printf("%zu\n", ARRAYSIZE(b)); printf("%zu\n", ARRAYSIZE(c)); return 0; } 

Вывод:

 5 0 10 

Как отметил Бен jackson, вы можете заставить исключение во время выполнения (деление на 0)

 #define IS_INDEXABLE(arg) (sizeof(arg[0])) #define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg))) #define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0)) 

К сожалению, вы не можете принудительно выполнить ошибку времени компиляции (адрес arg должен сравниваться во время выполнения)

С C11 мы можем различать массивы и указатели, используя _Generic , но я нашел способ сделать это, если вы _Generic тип элемента:

 #define ARRAY_SIZE(A, T) \ _Generic(&(A), \ T **: (void)0, \ default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0]))) int a[2]; printf("%zu\n", ARRAY_SIZE(a, int)); 

Макрос проверяет: 1) указатель-на-A не является указателем на указатель. 2) pointer-to-elem является указателем на T. Он оценивает (void)0 и статически статирует указатели.

Это неполный ответ, но, возможно, читатель может улучшить его и избавиться от этого параметра!

Модификация ответа bluss с использованием typeof вместо параметра типа:

 #define ARRAY_SIZE(A) \ _Generic(&(A), \ typeof((A)[0]) **: (void)0, \ default: sizeof(A) / sizeof((A)[0])) 

Вот еще один, который опирается на расширение gcc typeof :

 #define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \ sizeof(arr) / sizeof(arr[0]);}) 

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

 arraysize.c: In function 'main': arraysize.c:11: error: array index in non-array initializer arraysize.c:11: error: (near initialization for 'p_is_a_pointer') 

Вот одно возможное решение с использованием расширения GNU, называемого выражения выражения :

 #define ARRAYSIZE(arr) \ ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \ sizeof(arr) / sizeof((arr)[0]);}) 

Это использует статическое утверждение для утверждения sizeof(arr) != sizeof(void*) . У этого есть очевидное ограничение – вы не можете использовать этот макрос на массивах, размер которых является точно одним указателем (например, массив длиной 1 строки указателей / целых чисел или, возможно, массив длиной 4 длины в 32-битном Платформа). Но эти конкретные случаи могут быть проделаны достаточно легко.

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

Ужасно, да, но это работает, и оно переносимо.

 #define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \ (sizeof(arr)/sizeof(*arr)) : \ -1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__)) 

Это не обнаружит ничего во время компиляции, но выведет сообщение об ошибке в stderr и возвращает -1 если это указатель или длина массива равна 1.

==> DEMO <==

мой личный фаворит, попробовал gcc 4.6.3 и 4.9.2:

 #define STR_(tokens) # tokens #define ARRAY_SIZE(array) \ ({ \ _Static_assert \ ( \ ! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \ "ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \ ); \ sizeof(array) / sizeof((array)[0]); \ }) /* * example */ #define not_an_array ((char const *) "not an array") int main () { return ARRAY_SIZE(not_an_array); } 

печать компилятора

 xc:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array" 

Еще один пример коллекции.

 #define LENGTHOF(X) ({ \ const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \ typeof(X[0]) (*should_be_an_array)[length] = &X; \ length; }) 

Плюсы:

  1. Он работает с обычными массивами, массивами переменной длины, многомерными массивами, массивами структур нулевого размера
  2. Он генерирует ошибку компиляции (не предупреждение), если вы передаете любой указатель, структуру или объединение
  3. Он не зависит от каких-либо функций C11
  4. Это дает вам очень читаемую ошибку

Минусы:

  1. Это зависит от некоторых расширений gcc: Typeof , Statement Exprs и (если вам это нравится) Условные
  2. Это зависит от функции VLA C99
  • Как заполнить массивы на Java?
  • jQuery .inArray () всегда верно?
  • Получите все уникальные значения в массиве JavaScript (удалите дубликаты)
  • Доступ к массивам по индексу в C и C ++
  • Java-создание байтового массива, размер которого представлен длинным
  • Найти сходство косинусов между двумя массивами
  • Java читает файл и сохраняет текст в массиве
  • JavaScript: разница между .forEach () и .map ()
  • Swift Array дополнительно Тип и подписка (бета-версия 3)
  • Разбить строку на ассоциативный массив без использования циклов?
  • получение размера массива из указателя c ++
  • Interesting Posts

    Матричное умножение: малая разница в размере матрицы, большая разница в таймингах

    Зачем мне обновлять свой IP-адрес каждый раз, когда я запускаю свой компьютер, прежде чем я смогу получить доступ в Интернет?

    Выполнить обновление (пересканирование) беспроводных сетей из командной строки?

    Советы и трюки Vim и Ctags

    Как повторно включить backspace-навигацию в Chrome?

    как объединить повторяющиеся строки и суммировать значения 3 столбца в excel

    Виртуальный экран VNC / рабочее пространство

    Разрешение не доверенных SSL-сертификатов с помощью HttpClient

    Изменение режима доступа к функциям в производном classе

    Outlook 2007 – Самый простой и самый недорогой способ поделиться календарем между 5-10 людьми?

    Совместное использование буфера обмена Virtualbox

    C ++ 11 Regex Matching

    Как присоединиться к двум основным разделам окон 10

    Почему SetWindowsHookEx должен использоваться с очередью сообщений Windows

    Размер шрифта относительно разрешения экрана пользователя?

    Давайте будем гением компьютера.