Использование стандартного массива в C с естественным распадом типа от массива к ptr
@Bo Persson правильно заявляет в своем замечательном ответе здесь :
При передаче массива в качестве параметра это
void arraytest(int a[])
означает то же самое, что и
void arraytest(int *a)
Однако позвольте мне также добавить, что две вышеуказанные формы также:
означает то же самое, что и
void arraytest(int a[0])
что означает то же самое, что и
void arraytest(int a[1])
что означает то же самое, что и
void arraytest(int a[2])
что означает то же самое, что и
void arraytest(int a[1000])
и т.п.
В каждом из приведенных выше примеров массивов тип входного параметра распадается наint *
, и его можно вызывать без предупреждений и ошибок, даже если -Wall -Wextra -Werror
включены параметры сборки (см. Мое репо здесь для получения подробной информации об этих трех вариантах сборки), например этот:
int array1[2];
int * array2 = array1;
arraytest(array1);
arraytest(array2);
В самом деле, значение «размера» ( [0]
, [1]
, [2]
, [1000]
и т.д.) внутри параметра массива здесь, по- видимому только для целей эстетического / самодокументирования, и может быть любым положительным целым числом ( size_t
типа я думаю) вы хотите!
На практике, однако, вы должны использовать его для указания минимального размера массива, который вы ожидаете от функции, чтобы при написании кода вам было легко отслеживать и проверять. Стандарт MISRA-C-2012 ( купить / загрузить версию стандарта на 236 стр. 2012 в формате PDF за 15 фунтов стерлингов здесь ) заходит так далеко, что заявляет (выделено мной):
Правило 17.5 Аргумент функции, соответствующий параметру, объявленному как имеющий тип массива, должен иметь соответствующее количество элементов.
...
Если параметр объявлен как массив с указанным размером, соответствующий аргумент в каждом вызове функции должен указывать на объект, который имеет как минимум такое же количество элементов, как и массив.
...
Использование декларатора массива для параметра функции более четко определяет интерфейс функции, чем использование указателя. Минимальное количество элементов, ожидаемых функцией, явно указано, тогда как с указателем это невозможно.
Другими словами, они рекомендуют использовать явный формат размера, даже несмотря на то, что стандарт C технически не требует его соблюдения - он, по крайней мере, помогает прояснить вам как разработчику и другим, использующим код, массив размера, ожидаемого функцией. вам пройти.
Принудительная безопасность типов для массивов в C
Как отмечает @Winger Sendon в комментарии под моим ответом, мы можем заставить C рассматривать тип массива как другой в зависимости от размера массива !
Во-первых, вы должны признать, что в моем примере, приведенном выше, используя int array1[2];
подобное: arraytest(array1);
вызывает array1
автоматическое преобразование в файл int *
. ОДНАКО, если вы вместо этого возьмете адрес array1
или позвоните arraytest(&array1)
, вы получите совершенно другое поведение! Теперь он НЕ распадается на int *
! Вместо этого тип &array1
is int (*)[2]
, что означает «указатель на массив размера 2 типа int» или «указатель на массив размера 2 типа int» . Итак, вы можете FORCE C проверить безопасность типов в массиве, например:
void arraytest(int (*a)[2])
{
}
Этот синтаксис трудно читать, но он похож на синтаксис указателя на функцию . Онлайн-инструмент cdecl сообщает нам, что это int (*a)[2]
означает: «объявить a как указатель на массив 2 из int» (указатель на массив 2 int
с). НЕ путайте это с версией со скобками OUT:, int * a[2]
что означает: «объявить a как массив 2 указателя на int» (массив из 2 указателей на int
).
Теперь эта функция ТРЕБУЕТ, чтобы вы вызывали ее с помощью адресного оператора ( &
), подобного этому, используя в качестве входного параметра УКАЗАТЕЛЬ НА МАССИВ ПРАВИЛЬНОГО РАЗМЕРА !:
int array1[2];
arraytest(&array1);
Однако это приведет к предупреждению:
int array1[2];
arraytest(array1);
Вы можете протестировать этот код здесь .
Чтобы заставить компилятор C превратить это предупреждение в ошибку, чтобы вы всегда ДОЛЖНЫ вызывать, arraytest(&array1);
используя только входной массив правильного размера и типа ( int array1[2];
в данном случае), добавьте -Werror
в параметры сборки. Если вы запускаете приведенный выше тестовый код на сайте onlinegdb.com, сделайте это, щелкнув значок шестеренки в правом верхнем углу и щелкнув «Дополнительные флаги компилятора», чтобы ввести этот параметр. Теперь это предупреждение:
main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
превратится в эту ошибку сборки:
main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
arraytest(array1);
^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
void arraytest(int (*a)[2])
^~~~~~~~~
cc1: all warnings being treated as errors
Обратите внимание, что вы также можете создавать «типобезопасные» указатели на массивы заданного размера, например:
int array[2];
int (*array_p)[2] = &array;
... но я не обязательно рекомендую это, так как это напоминает мне многие выходки C ++, используемые для обеспечения безопасности типов повсюду, за исключительно высокую цену сложности синтаксиса языка, многословия и сложности разработки кода, и которые мне не нравятся и неоднократно высказывались ранее (например, см. «Мои мысли о C ++» здесь ).
Для дополнительных тестов и экспериментов см. Также ссылку чуть ниже.
Ссылки
См. Ссылки выше. Также:
- Мои эксперименты с кодом в Интернете: https://onlinegdb.com/B1RsrBDFD