Передача масиву як аргументу функції в 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 - Я б використав другий варіант, оскільки він здається менш заплутаним і краще вказує на те, що ви не отримуєте копію масиву.
Бо Перссон,

2
Чи можете ви пояснити "історичні причини"? Думаю, для передачі значень потрібна копія, а отже, втрата пам’яті .. дякую
Jacquelyn.Marquardt

5
@lucapozzobon - Спочатку C не мав жодного переданого значення, крім окремих значень. Лише тоді, коли structдо мови додали цю зміну. І тоді було визнано занадто пізно змінювати правила для масивів. Вже було 10 користувачів. :-)
Бо Перссон

1
... означає точно те саме, що void arraytest(int a[1000])тощо тощо. Розширена відповідь тут: stackoverflow.com/a/51527502/4561887 .
Габріель Стейплз

9

Якщо ви хочете передати одновимірний масив як аргумент у функції , вам доведеться оголосити формальний параметр одним із наступних трьох способів, і всі три методи оголошення дають подібні результати, оскільки кожен повідомляє компілятору, що цілочисельний вказівник збирається до отримання .

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

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

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

Отже, ви змінюєте початкові значення.

Дякую !!!


Я шукав ваш другий приклад, чи можете ви пояснити, в чому переваги кожного методу?
Шайба

8

Ви не передаєте масив як копію. Це лише покажчик, що вказує на адресу, де перший елемент масиву знаходиться в пам'яті.


7

Ви передаєте адресу першого елемента масиву


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, за винятком кількох особливих випадків, посилання на масив завжди "розпадається" на покажчик на перший елемент масиву. Тому передавати масив "за значенням" неможливо. Масив у виклику функції буде переданий функції як покажчик, що є аналогом передачі масиву за посиланням.

EDIT: Є три таких особливих випадки, коли масив не перетворюється на покажчик на свій перший елемент:

  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)відповідь, буде призначений покажчик байтів. Тож у цьому випадку він занепадає як вказівник. Було б корисно, якщо б ви також включили це. Дивіться це postimg.org/image/prhleuezd
Suraj Jain

Якщо я використовую sizeofоперувати функцією у спочатку визначеному нами масиві, то він занепадає як масив, але якщо я передаю іншу функцію, тоді sizeofоператор використання він занепадає як вказівник.
Suraj Jain


Я знаю, що це старе. Два запитання, якщо хтось випадково це бачить :) 1. @ThomSmith писав, що &aце не зовсім те саме, що &a[0]коли aце масив. Як так? У моїй тестовій програмі обидва виявляються однаковими, як у функції, де оголошено масив, так і при передачі в іншу функцію. 2. Письменник пише, що « char b[] = "foo"це не те саме, що char b[] = &("foo")». Для мене остання навіть не компілюється. Це лише я?
Авів Кон

6

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

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]
...

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

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 елементів, anint(*arr)[6] - вказівник на масив з 6 елементів, і вони є вказівниками на різні типи!

Ну, як визначити аргументи функцій для вищих вимірів? Просто, ми просто слідуємо шаблону! Ось та сама функція, налаштована на прийняття масиву з 3-х вимірів:

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 Перссон правильно стверджує у своїй чудовій відповіді тут :

При передачі масиву як параметра, це

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увімкненими параметрами збірки (див. Моє репо тут для деталей цих 3 варіантів збірки), наприклад це:

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 (тут придбайте / завантажте PDF із стандартом 236-сторінкової версії 2012 року за 15,00 фунтів стерлінгів ) доходить до ствердження (курсив додано):

Правило 17.5. Аргумент функції, що відповідає параметру, який оголошений типом масиву, повинен мати відповідну кількість елементів.

...

Якщо параметр оголошено як масив із заданим розміром, відповідний аргумент у кожному виклику функції повинен вказувати на об'єкт, що має принаймні стільки елементів, скільки масив.

...

Використання декларатора масиву для параметра функції задає інтерфейс функції більш чітко, ніж використання вказівника. Мінімальна кількість елементів, що очікується функцією, чітко зазначена, тоді як це неможливо за допомогою покажчика.

Іншими словами, вони рекомендують використовувати формат явного розміру, хоча технічно стандарт С не застосовує його - він принаймні допомагає пояснити вам як розробнику та іншим, хто використовує код, який масив розміру очікує функція вам пройти.


Примусове забезпечення типу масивів у 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 ints). НЕ плутайте це з версією з дужками OUT:, int * a[2]що означає: "оголосити масив 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])краще, тому що тоді компілятор помилиться, якщо розмір неправильний.
Вінгер Сендон

@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.