Массив размера массива, который отклоняет указатели
Обычно широко используемый макрос размера массива
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
или некоторое эквивалентное образование. Тем не менее, такого рода вещи бесшумно преуспевают, когда передается указатель, и дает результаты, которые могут показаться правдоподобными во время выполнения, пока вещи не таинственно разваливаются.
Слишком легко сделать эту ошибку: функция, которая имеет переменную локального массива, реорганизуется, перемещая немного манипуляции с массивом в новую функцию, называемую массивом в качестве параметра.
- Как объявить 2d-массив в C ++ с помощью new?
- Ошибка прерывания ловушки 6 в C
- Словарь в Swift с Mutable Array как значение работает очень медленно? Как оптимизировать или построить правильно?
- c ++ sort с structs
- Как сделать массив массивов в Java
Итак, возникает вопрос: существует ли «санитарный» макрос для обнаружения неправильного использования макроса ARRAYSIZE
в C, желательно во время компиляции? В C ++ мы просто использовали шаблон, специализированный только для аргументов массива; в C, кажется, нам нужно каким-то образом различать массивы и указатели. (Если бы я хотел отклонить массивы, например, я бы просто сделал eg (arr=arr, ...)
потому что назначение массива является незаконным).
- Ожидаемое постоянное выражение C ++
- Как преобразовать массив байтов в шестнадцатеричную строку и наоборот?
- Массивы с концевыми запятыми внутри инициализатора массива в Java
- DIR в массив BAT?
- Как реализована длина в массивах Java?
- Как обновить несколько элементов массива в mongodb
- Неизменяемый массив в Java
- Почему Collections.sort использует Mergesort, но Arrays.sort нет?
Ядро 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; })
Плюсы:
- Он работает с обычными массивами, массивами переменной длины, многомерными массивами, массивами структур нулевого размера
- Он генерирует ошибку компиляции (не предупреждение), если вы передаете любой указатель, структуру или объединение
- Он не зависит от каких-либо функций C11
- Это дает вам очень читаемую ошибку
Минусы:
- Это зависит от некоторых расширений gcc: Typeof , Statement Exprs и (если вам это нравится) Условные
- Это зависит от функции VLA C99