Unterschied zwischen der Übergabe von Array und Array-Zeiger an die Funktion in C.

110

Was ist der Unterschied zwischen den beiden Funktionen in C?

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

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

Wenn ich die Funktionen in einem wesentlich langen Array aufrufen würde, würden sich diese beiden Funktionen unterschiedlich verhalten, würden sie mehr Platz auf dem Stapel beanspruchen?

Kaushik Shankar
quelle

Antworten:

114

Zunächst einige Standardese :

6.7.5.3 Funktionsdeklaratoren (einschließlich Prototypen)
...
7 Eine Deklaration eines Parameters als '' Array vom Typ '' wird an '' qualifizierter Zeiger auf Typ '' angepasst , wobei die Typqualifizierer (falls vorhanden) die angegebenen sind innerhalb der [und ]der Array-Typ-Ableitung. Wenn das Schlüsselwort staticauch in der Ableitung [und ]des Array-Typs vorkommt, muss der Wert des entsprechenden tatsächlichen Arguments bei jedem Aufruf der Funktion den Zugriff auf das erste Element eines Arrays mit mindestens so vielen Elementen ermöglichen, wie durch die Größe angegeben Ausdruck.

Kurz gesagt, jeder Funktionsparameter, der als deklariert T a[]oder T a[N]behandelt wird, wird so behandelt, als ob er deklariert worden wäre T *a.

Warum werden Array-Parameter so behandelt, als wären sie als Zeiger deklariert? Hier ist der Grund:

6.3.2.1 L-Werte, Arrays und Funktionsbezeichner
...
3 Außer wenn es sich um den Operanden des sizeofOperators oder des unären &Operators handelt oder um ein Zeichenfolgenliteral, das zum Initialisieren eines Arrays verwendet wird, einen Ausdruck vom Typ '' Array vom Typ ' 'wird in einen Ausdruck mit dem Typ' 'Zeiger auf Typ ' ' konvertiert, der auf das Anfangselement des Array-Objekts zeigt und kein l-Wert ist. Wenn das Array-Objekt eine Registerspeicherklasse hat, ist das Verhalten undefiniert.

Gegeben den folgenden Code:

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

Beim Aufruf von fooist der Array-Ausdruck arrkein Operand von entweder sizeofoder &, daher wird sein Typ gemäß 6.2.3.1/3 implizit von "10-Element-Array von int" in "Zeiger auf int" konvertiert. Somit foowird eher ein Zeigerwert als ein Arraywert empfangen.

Aufgrund von 6.7.5.3/7 können Sie schreiben fooals

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

aber es wird interpretiert als

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

Somit sind die beiden Formen identisch.

Der letzte Satz in 6.7.5.3/7 wurde mit C99 eingeführt und bedeutet im Grunde, dass, wenn Sie eine Parameterdeklaration wie haben

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

Der entsprechende tatsächliche Parameter amuss ein Array mit mindestens 10 Elementen sein.

John Bode
quelle
1
Bei der Verwendung von (zumindest einigen älteren) MSVC C ++ - Compilern gibt es einen Unterschied, da der Compiler den Funktionsnamen in beiden Fällen fälschlicherweise unterschiedlich entstellt (obwohl er erkennt, dass sie ansonsten identisch sind), was zu Verbindungsproblemen führt. Siehe "Fehlerbehebung nicht" Fehlerbericht hier connect.microsoft.com/VisualStudio/feedback/details/326874/…
Greggo
29

Der Unterschied ist rein syntaxisch. Wenn in C die Array-Notation für einen Funktionsparameter verwendet wird, wird sie automatisch in eine Zeigerdeklaration umgewandelt.

Thomas Pornin
quelle
1
@ Kaushik: Obwohl sie in diesem Fall gleich sind, denken Sie daran, dass sie im allgemeinen Fall
BlueRaja - Danny Pflughoeft
@BlueRaja: Ja, es ist eine der Fallstricke von C. Die Deklaration von Funktionsparametern ist der Deklaration lokaler Variablen sehr ähnlich , aber es gibt einige subtile Unterschiede (wie diese automatische Transformation von Array zu Zeiger) neigt dazu, den unachtsamen Programmierer zu beißen.
Thomas Pornin
0

Nein, es gibt keinen Unterschied zwischen ihnen. Zum Testen habe ich diesen C-Code im Dev C ++ (mingw) Compiler geschrieben:

#include <stdio.h>

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

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

Wenn ich disassemble Hauptfunktion in .exe beide Aufruf Versionen von Binärdatei in IDA erhalte ich genau den gleichen Assembler - Code wie folgt:

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

Es gibt also keinen Unterschied zwischen den beiden Versionen dieses Aufrufs, zumindest bedroht der Compiler sie gleichermaßen.

Caltuntas
quelle
18
Entschuldigung, aber dies beweist nur, dass eine Version von gcc auf x86 für beide dieselbe Assembly generiert. Richtige Antwort, falsche Erklärung.
Lambdapower