Выражения массива являются специальными в C.
6.3.2.1 L-значения, массивы и обозначения функций
...
3 За исключением случаев, когда это операнд оператора sizeof
, оператор _Alignof
или унарный оператор &
, или строковый литерал, используемый для инициализации массива, выражение с типом '' массив типа '' преобразуется в выражение с типом '' указатель на тип '', которое указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено.
C 2011 онлайн-черновик
При звонке
set_array(b);
выражение b
преобразуется из типа "массив из 4 элементов int
" в "указатель на int
" (int *
), а значением выражения является адрес b[0]
, Следовательно, set_array
получает только значение указателя (&b[0]
), а не массив.
6.7.6.3 Деклараторы функций (включая прототипы)
...
7 Объявление параметра как «массива типа» должно быть скорректировано на «квалифицированный указатель на тип», где квалификаторы типа (если таковые имеются) - те, которые указаны в [
и ]
вывода типа массива. Если ключевое слово static
также присутствует в [
и ]
деривации типа массива, то при каждом вызове функции значение соответствующего фактического аргумента должно обеспечивать доступ к первому элементу массива с at наименьшее количество элементов, указанное в выражении размера.
там же.
По сути, в определении функции любой параметр, объявленный как T a[N]
или T a[]
должно интерпретироваться как T *a
- IOW, параметр обрабатывается как указатель, а не как массив. Ваше определение функции
void set_array(int array[4]){
array[0] = 22;
}
обрабатывается так, как если бы оно было написано
void set_array(int *array){
array[0] = 22;
}
Таким образом, массивы вроде как-то-но-не-действительно передаются по ссылке в C. Что действительно происходит, так это то, что указатель на первый элемент массива передается по значению . Параметр array
в set_array
обозначает отдельный объект в памяти от b
, поэтому любые изменения, которые вы вносите в array
, сам не влияют на b
(IOW, вы можете назначить новое значение в array
и оно указывает на другой объект, но это назначение вообще не влияет на b
). Вместо этого вы обращаетесь к элементам с b
по array
.
В виде изображения:
+---+
b: | | b[0] <---+
+---+ |
| | b[1] |
+---+ |
| | b[2] |
+---+ |
| | b[3] |
+---+ |
... |
+---+ |
array: | | ---------+
+---+
Одним из практических следствий этого является то, что sizeof array
возвращает размер указателя типа int *
, а не размер массива, в отличие от sizeof b
. Это означает, что вы не можете подсчитать количество элементов в массиве, используя трюк sizeof array / sizeof array[0]
. Когда вы передаете массив в качестве аргумента функции, вы также должны передать размер массива (то есть количество элементов) в качестве отдельного параметра , если массив не содержит четко определенного значения часового (например, терминатор 0 в строках).
Помните, что выражение a[i]
определено как *(a + i)
- с учетом начального адреса a
, смещение i
элементов (не байтов!) с этого адреса и почтите результат. Если a
является выражением массива, оно сначала преобразуется в выражение указателя перед выполнением этого вычисления. Вот почему вы можете получить доступ к элементам с b
по array
- array
просто хранит адрес первого элемента b
.
В C, все аргументы функции передаются по значению - формальный аргумент в определении функции и фактический аргумент в вызове функции ссылаются на различные объекты в памяти, а значение фактического аргумента копируется в формальный аргумент. Изменения формального аргумента не отражаются в фактическом аргументе.
Мы подделываем семантику передачи по ссылке, передавая указатель фактическому параметру и записывая в разыменованный указатель:
void foo( T *ptr )
{
*ptr = new_value(); // write a new value to the thing ptr points to
}
void bar( void )
{
T var;
foo( &var ); // have foo write a new value to var
}
IOW, записывая *ptr
- это то же самое, что писать var
. Запись в ptr
, OTOH, никак не влияет на foo
.
В истинной системе передачи по ссылке (например, Fortran) и формальный аргумент в определении функции, и фактический аргумент в вызове функции обозначают один и тот же объект (в «вещи, которая занимает памяти и может хранить значения "смысл, а не объектно-ориентированный" экземпляр класса "смысл), поэтому в этих системах любые изменения формального аргумента отражаются в фактическом аргументе (приводящем к классу c вопрос по хакерскому тесту : «Вы когда-нибудь меняли значение 4
? Неумышленно? На языке, отличном от Fortran?»)