Передача массива в качестве аргумента функции в C


86

Я написал функцию, содержащую массив в качестве аргумента, и вызвал ее, передав значение массива следующим образом.

void arraytest(int a[])
{
    // changed the array a
    a[0]=a[0]+a[1];
    a[1]=a[0]-a[1];
    a[0]=a[0]-a[1];
}

void main()
{
    int arr[]={1,2};
    printf("%d \t %d",arr[0],arr[1]);
    arraytest(arr);
    printf("\n After calling fun arr contains: %d\t %d",arr[0],arr[1]);
}

Я обнаружил, что хотя я вызываю arraytest()функцию, передавая значения, исходная копия int arr[]изменяется.

Не могли бы вы объяснить почему?


1
Вы передаете массив по ссылке, но изменяете его содержимое - поэтому вы видите изменение данных
Шон Уайльд

main()должен вернуться int.
underscore_d

Ответы:


137

При передаче массива в качестве параметра это

void arraytest(int a[])

означает то же самое, что и

void arraytest(int *a)

так что вы являетесь изменения значений в основном.

По историческим причинам массивы не являются гражданами первого класса и не могут передаваться по значению.


3
Какие обозначения лучше при каких обстоятельствах?
Рамон Мартинес

20
@Ramon - я бы использовал второй вариант, поскольку он кажется менее запутанным и лучше указывает на то, что вы не получаете копию массива.
Bo Persson

2
Вы можете объяснить «исторические причины»? Я полагаю, что для перехода по значениям потребуется копия, а значит, пустая трата памяти .. спасибо
Jacquelyn.Marquardt

5
@lucapozzobon - Первоначально в C не было передачи по значению, кроме одиночных значений. Это structбыло изменено только после того, как был добавлен язык. И тогда было уже поздно менять правила для массивов. Пользователей уже было 10. :-)
Bo Persson

1
... означает то же самое, что и void arraytest(int a[1000])т.д. и т.п. Расширенный ответ здесь: stackoverflow.com/a/51527502/4561887 .
Gabriel Staples

9

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

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

Итак, вы изменяете исходные значения.

Благодарность !!!


Я искал ваш второй пример, можете ли вы рассказать о преимуществах каждого метода?
Пак

8

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



7

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

Вот цитата из K & R2nd :

Когда имя массива передается в функцию, передается местоположение исходного элемента. Внутри вызываемой функции этот аргумент является локальной переменной, поэтому параметр имени массива является указателем, то есть переменной, содержащей адрес.

Письмо:

void arraytest(int a[])

имеет то же значение, что и письмо:

void arraytest(int *a)

Итак, несмотря на то, что вы не пишете это явно, это так, как вы передаете указатель, и поэтому вы изменяете значения в файле main.

Для получения дополнительной информации я действительно рекомендую прочитать это .

Более того, вы можете найти другие ответы на SO здесь


6

Вы передаете значение ячейки памяти первого члена массива.

Поэтому, когда вы начинаете изменять массив внутри функции, вы изменяете исходный массив.

Помните, что a[1]это так *(a+1).


1
Я полагаю, что отсутствуют () для * a + 1 должно быть * (a + 1)
ShinTakezou

@Shin Спасибо, я давно не играл с C.
alex

6

В C, за исключением нескольких особых случаев, ссылка на массив всегда «распадается» на указатель на первый элемент массива. Следовательно, невозможно передать массив «по значению». Массив в вызове функции будет передан в функцию как указатель, что аналогично передаче массива по ссылке.

РЕДАКТИРОВАТЬ: есть три таких особых случая, когда массив не распадается на указатель на его первый элемент:

  1. sizeof aне то же самое, что sizeof (&a[0]).
  2. &aне то же самое &(&a[0])(и не совсем то же самое &a[0]).
  3. char b[] = "foo"не то же самое, что char b[] = &("foo").

Если я передаю массив функции. Скажем, например, я создал массив int a[10]и присвоил каждому элементу случайное значение. Теперь, если я передаю этот массив функции с помощью int y[]или int y[10]или. int *yИ тогда в этой функции, которую я использую, sizeof(y)Answer будет выделен указатель байтов. Так что в этом случае он будет распадаться как указатель. Было бы полезно, если бы вы его тоже включили. См. Postimg.org/image/prhleuezd
Сурадж Джайн

Если я использую sizeofоперацию в функции в первоначально определенном нами массиве, тогда он распадется как массив, но если я передам другую функцию, тогда используется sizeofоператор, он распадется как указатель.
Сурадж Джайн


Я знаю, что это старое. Два вопроса, если кто-нибудь это увидит :) 1. @ThomSmith написал, что &aэто не совсем то же самое, что &a[0]когда aэто массив. Как так? В моей тестовой программе оба показываются одинаковыми как в функции, в которой объявлен массив, так и при передаче в другую функцию. 2. Писатель пишет, что « char b[] = "foo"не то же самое char b[] = &("foo")». Для меня последний даже не компилируется. Это только я?
Авив Кон,

6

Передача многомерного массива в качестве аргумента функции. Передача однотонного массива в качестве аргумента более или менее тривиальна. Давайте рассмотрим более интересный случай передачи двухмерного массива. В C вы не можете использовать указатель на конструкцию указателя ( int **) вместо двух тусклых массивов. Приведем пример:

void assignZeros(int(*arr)[5], const int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 5; j++) {
            *(*(arr + i) + j) = 0;
            // or equivalent assignment
            arr[i][j] = 0;
        }
    }

Здесь я указал функцию, которая принимает в качестве первого аргумента указатель на массив из 5 целых чисел. Я могу передать в качестве аргумента любой 2 тусклый массив с 5 столбцами:

int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...

Вам может прийти в голову идея определить более общую функцию, которая может принимать любой 2-тусклый массив и изменять подпись функции следующим образом:

void assignZeros(int ** arr, const int rows, const int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            *(*(arr + i) + j) = 0;
        }
    }
}

Этот код будет компилироваться, но вы получите ошибку времени выполнения при попытке присвоить значения таким же образом, как и в первой функции. Итак, в C многомерные массивы - это не то же самое, что указатели на указатели ... на указатели. An int(*arr)[5]- указатель на массив из 5 элементов, an int(*arr)[6] - указатель на массив из 6 элементов, и они являются указателями на разные типы!

Хорошо, как определить аргументы функций для более высоких измерений? Все просто, мы просто следуем шаблону! Вот та же функция, настроенная для использования массива из трех измерений:

void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3) {
    for (int i = 0; i < dim1; i++) {
        for (int j = 0; j < dim2; j++) {
            for (int k = 0; k < dim3; k++) {
                *(*(*(arr + i) + j) + k) = 0;
                // or equivalent assignment
                arr[i][j][k] = 0;
            }
        }
    }
}

Как и следовало ожидать, он может принимать в качестве аргумента любые 3 тусклых массива, которые имеют во втором измерении 4 элемента и в третьем измерении 5 элементов. Что-нибудь вроде этого было бы нормально:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...

Но мы должны указать все размеры, вплоть до первого.


5

Использование стандартного массива в C с естественным распадом типа от массива к ptr

@Bo Persson правильно заявляет в своем замечательном ответе здесь :

При передаче массива в качестве параметра это

void arraytest(int a[])

означает то же самое, что и

void arraytest(int *a)

Однако позвольте мне также добавить, что две вышеуказанные формы также:

  1. означает то же самое, что и

     void arraytest(int a[0])
    
  2. что означает то же самое, что и

     void arraytest(int a[1])
    
  3. что означает то же самое, что и

     void arraytest(int a[2])
    
  4. что означает то же самое, что и

     void arraytest(int a[1000])
    
  5. и т.п.

В каждом из приведенных выше примеров массивов тип входного параметра распадается наint * , и его можно вызывать без предупреждений и ошибок, даже если -Wall -Wextra -Werrorвключены параметры сборки (см. Мое репо здесь для получения подробной информации об этих трех вариантах сборки), например этот:

int array1[2];
int * array2 = array1;

// works fine because `array1` automatically decays from an array type
// to `int *`
arraytest(array1);
// works fine because `array2` is already an `int *` 
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 *! Вместо этого тип &array1is int (*)[2], что означает «указатель на массив размера 2 типа int» или «указатель на массив размера 2 типа int» . Итак, вы можете FORCE C проверить безопасность типов в массиве, например:

void arraytest(int (*a)[2])
{
    // my function here
}

Этот синтаксис трудно читать, но он похож на синтаксис указателя на функцию . Онлайн-инструмент cdecl сообщает нам, что это int (*a)[2]означает: «объявить a как указатель на массив 2 из int» (указатель на массив 2 intс). НЕ путайте это с версией со скобками OUT:, int * a[2]что означает: «объявить a как массив 2 указателя на int» (массив из 2 указателей на int).

Теперь эта функция ТРЕБУЕТ, чтобы вы вызывали ее с помощью адресного оператора ( &), подобного этому, используя в качестве входного параметра УКАЗАТЕЛЬ НА МАССИВ ПРАВИЛЬНОГО РАЗМЕРА !:

int array1[2];

// ok, since the type of `array1` is `int (*)[2]` (ptr to array of 
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
                    // `array1` from otherwise automatically decaying
                    // into `int *`, which is the WRONG input type here!

Однако это приведет к предупреждению:

int array1[2];

// WARNING! Wrong type since the type of `array1` decays to `int *`:
//      main.c:32:15: warning: passing argument 1 of ‘arraytest’ from 
//      incompatible pointer type [-Wincompatible-pointer-types]                                                            
//      main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)

Вы можете протестировать этот код здесь .

Чтобы заставить компилятор 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); // warning!
               ^~~~~~
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];
// "type safe" ptr to array of size 2 of int:
int (*array_p)[2] = &array;

... но я не обязательно рекомендую это, так как это напоминает мне многие выходки C ++, используемые для обеспечения безопасности типов повсюду, за исключительно высокую цену сложности синтаксиса языка, многословия и сложности разработки кода, и которые мне не нравятся и неоднократно высказывались ранее (например, см. «Мои мысли о C ++» здесь ).


Для дополнительных тестов и экспериментов см. Также ссылку чуть ниже.

Ссылки

См. Ссылки выше. Также:

  1. Мои эксперименты с кодом в Интернете: https://onlinegdb.com/B1RsrBDFD

2
void arraytest(int (*a)[1000])лучше, потому что тогда компилятор выдаст ошибку, если размер неправильный.
Winger Sendon

@WingerSendon, я знал, что здесь есть некоторые тонкости, которые мне нужно проверить, и этот синтаксис сбивает с толку (например, сбивает синтаксис функции ptr), поэтому я не торопился и, наконец, обновил свой ответ большим новым разделом под названием Forcing type safety on arrays in C, охватывающим ваши точка.
Габриэль Стейплс,

0

Массивы всегда передаются по ссылке, если вы используете a[]или *a:

int* printSquares(int a[], int size, int e[]) {   
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}

int* printSquares(int *a, int size, int e[]) {
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}

Я поддерживаю это. Я не уверен, почему он отвергнут.
Габриэль Стейплс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.