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

110

В чем разница между двумя функциями в C?

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 Значения L, массивы и указатели функций
...
3 За исключением случаев, когда это операнд sizeofоператора или унарный &оператор, или строковый литерал, используемый для инициализации массива, выражение, имеющее тип "массив типа " 'преобразуется в выражение с типом' 'указатель на тип ' ', которое указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено.

Учитывая следующий код:

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 - Дэнни Пфлугофт
@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

Таким образом, нет никакой разницы между двумя версиями этого вызова, по крайней мере, компилятор угрожает им одинаково.

Caltuntas
источник
18
Извините, но это только доказывает, что некоторая версия gcc генерирует одну и ту же сборку на x86 для обоих. Правильный ответ, неправильное объяснение.
lambdapower 01