Різниця між передаючим масивом і вказівником масиву на функцію в C


110

Яка різниця між двома функціями в С?

void f1(double a[]) {
   //...
}

void f2(double *a) {
   //...
}

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

Відповіді:


114

По-перше, деякі стандарти :

6.7.5.3 Декларатори функцій (включаючи прототипи)
...
7 Декларація параметра як "" масив типу "має бути налаштована на" кваліфікований покажчик на тип ", де кваліфіковані типи (якщо такі є) вказані в межах [і ]деривації типу масиву. Якщо ключове слово staticтакож відображається в [і ]виведенні типу масиву, то для кожного виклику функції значення відповідного фактичного аргументу забезпечує доступ до першого елемента масиву з щонайменше стільки ж елементів, скільки визначено розміром вираз.

Отже, коротше кажучи, будь-який параметр функції, оголошений як T a[]або T a[N]трактується так, ніби він був оголошений T *a.

Отже, чому параметри масиву трактуються так, ніби вони були оголошені як покажчики? Ось чому:

6.3.2.1 Значення, масиви та позначення функцій
...
3 За винятком випадків, коли це операнд sizeofоператора чи одинарний &оператор, або це рядковий літерал, який використовується для ініціалізації масиву, виразу, що має тип '' масив типу ' 'перетворюється у вираз із типом' 'покажчик на тип ' ', який вказує на початковий елемент об’єкта масиву і не є значенням. Якщо об’єкт масиву має клас зберігання в регістрі, поведінка не визначена.

Дано наступний код:

int main(void)
{
  int arr[10];
  foo(arr);
  ...
}

У виклику до foo, вираз масиву arrне є операндом жодного sizeofабо &, тому його тип неявно перетворюється з "10-елементного масиву int" в "покажчик на int" відповідно до 6.2.3.1/3. Таким чином, fooотримає значення вказівника, а не значення масиву.

Через 6.7.5.3/7 ви можете писати fooяк

void foo(int a[]) // or int a[10]
{
  ...
}

але це буде трактуватися як

void foo(int *a)
{
  ...
}

Таким чином, дві форми однакові.

Останнє речення в 6.7.5.3/7 було введено з C99, і в основному означає, що якщо у вас є оголошення параметра, як

void foo(int a[static 10])
{
  ...
}

фактичний параметр, що відповідає, aповинен бути масивом, що містить щонайменше 10 елементів.


1
Існує різниця при використанні (принаймні деяких старих) компіляторів MSVC C ++, через те, що компілятор неправильно керує іменем функції по-різному в двох випадках (при цьому визнаючи, що вони однакові інакше), що призводить до проблем з посиланням. Дивіться звіт про помилку "Не виправляю" тут connect.microsoft.com/VisualStudio/feedback/details/326874/…
greggo

29

Різниця суто синтаксична. У C, коли позначення масиву використовується для параметра функції, він автоматично перетворюється на оголошення вказівника.


1
@Kaushik: Хоча в цьому випадку вони однакові, майте на увазі, що в загальному випадку
BlueRaja - Danny Pflughoeft

@BlueRaja: так, це одна з підводних каменів C. Декларація параметрів функції дуже схожа на декларацію локальних змінних, але є кілька тонких відмінностей (наприклад, це автоматичне перетворення масиву в покажчик), які є схильний кусати необережного програміста.
Томас Порнін

0

Ні, різниці між ними немає. Для тестування я написав цей код C у компіляторі Dev C ++ (mingw):

#include <stdio.h>

void function(int* array) {
     int a =5;
}

void main() {  
     int array[]={2,4};
     function(array);
     getch();
}

Коли я розбираю основну функцію в .exe обох викликових версій бінарного файлу в IDA, я отримую точно такий же код складання, як нижче:

push    ebp
mov     ebp, esp
sub     esp, 18h
and     esp, 0FFFFFFF0h
mov     eax, 0
add     eax, 0Fh
add     eax, 0Fh
shr     eax, 4
shl     eax, 4
mov     [ebp+var_C], eax
mov     eax, [ebp+var_C]
call    sub_401730
call    sub_4013D0
mov     [ebp+var_8], 2
mov     [ebp+var_4], 4
lea     eax, [ebp+var_8]
mov     [esp+18h+var_18], eax
call    sub_401290
call    _getch
leave
retn

Тож різниці між двома версіями цього виклику немає, принаймні компілятор загрожує їм однаково.


18
Вибачте, але це лише доводить, що деяка версія gcc генерує однакову збірку на x86 для обох. Правильна відповідь, неправильне пояснення.
lambdapower
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.