То, что вы говорите в своем посте, абсолютно правильно. Я бы сказал, что каждый разработчик C приходит к одному и тому же открытию и приходит к одному и тому же выводу, когда (если) он достигает определенного уровня владения языком C.
Когда специфика вашей прикладной области вызывает массив определенного фиксированного размера (размер массива является константой времени компиляции), единственный правильный способ передать такой массив функции - использовать указатель на массив параметр
void foo(char (*p)[10]);
(на языке C ++ это также делается со ссылками
void foo(char (&p)[10]);
).
Это включит проверку типов на уровне языка, которая будет гарантировать, что массив точно правильного размера предоставляется в качестве аргумента. Фактически, во многих случаях люди используют эту технику неявно, даже не осознавая этого, скрывая тип массива за именем определения типа
typedef int Vector3d[3];
void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);
Кроме того, обратите внимание, что приведенный выше код инвариантен относительно типа Vector3d
, являющегося массивом, или struct
. Вы можете в любое время переключить определение Vector3d
с массива на struct
и обратно, и вам не придется изменять объявление функции. В любом случае функции получат агрегированный объект «по ссылке» (есть исключения из этого, но в контексте этого обсуждения это действительно так).
Однако вы не увидите, чтобы этот метод передачи массивов использовался явно слишком часто, просто потому, что слишком много людей смущаются из-за довольно запутанного синтаксиса и просто не достаточно удобны с такими возможностями языка Си, чтобы использовать их правильно. По этой причине в обычной реальной жизни передача массива в качестве указателя на его первый элемент является более популярным подходом. Это выглядит просто «проще».
Но на самом деле использование указателя на первый элемент для передачи массива является очень нишевой техникой, трюком, который служит очень конкретной цели: его единственная цель - облегчить передачу массивов разного размера (т.е. размер во время выполнения). Если вам действительно нужно иметь возможность обрабатывать массивы времени выполнения, то правильный способ передачи такого массива - указатель на его первый элемент с конкретным размером, указанным в дополнительном параметре
.
void foo(char p[], unsigned plen);
На самом деле, во многих случаях очень полезно иметь возможность обрабатывать массивы времени выполнения, что также способствует популярности метода. Многие разработчики на С просто никогда не сталкиваются (или никогда не осознают) необходимость обработки массива фиксированного размера, поэтому не обращают внимания на правильную технику фиксированного размера.
Тем не менее, если размер массива является фиксированным, передача его в качестве указателя на элемент
void foo(char p[])
является серьезной ошибкой уровня техники, которая, к сожалению, довольно распространена в наши дни. Техника указателя на массив в таких случаях гораздо лучше.
Другая причина, которая может помешать принятию техники передачи массивов фиксированного размера, заключается в преобладании наивного подхода к типизации динамически размещаемых массивов. Например, если программа вызывает фиксированные массивы типа char[10]
(как в вашем примере), средний разработчик будет malloc
таких массивов, как
char *p = malloc(10 * sizeof *p);
Этот массив не может быть передан функции, объявленной как
void foo(char (*p)[10]);
, что смущает обычных разработчиков и заставляет их отказаться от объявления параметров фиксированного размера, не задумываясь об этом. В действительности корень проблемы лежит в наивном подходе malloc
. Формат malloc
, показанный выше, должен быть зарезервирован для массивов времени выполнения. Если тип массива имеет размер времени компиляции, лучший способ malloc
будет выглядеть следующим образом:
char (*p)[10] = malloc(sizeof *p);
Это, конечно, можно легко передать выше заявленному foo
foo(p);
и компилятор выполнит правильную проверку типа. Но опять же, это слишком запутанно для неподготовленного разработчика C, поэтому вы не увидите его слишком часто в «типичном» обычном повседневном коде.