Differenza tra il passaggio di un array e un puntatore di array nella funzione in C


110

Qual è la differenza tra le due funzioni in C?

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

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

Se dovessi chiamare le funzioni su un array sostanzialmente lungo, queste due funzioni si comporterebbero in modo diverso, occuperebbero più spazio nello stack?

Risposte:


114

Innanzitutto, alcuni standard :

6.7.5.3 Dichiaratori di funzioni (inclusi i prototipi)
...
7 Una dichiarazione di un parametro come '' array di tipo '' deve essere adattata a '' puntatore qualificato al tipo '', dove i qualificatori di tipo (se presenti) sono quelli specificati all'interno del [e ]della derivazione del tipo di matrice. Se la parola chiave staticcompare anche all'interno del [e ]della derivazione del tipo di matrice, allora per ogni chiamata alla funzione, il valore dell'argomento effettivo corrispondente deve fornire l'accesso al primo elemento di un array con almeno tanti elementi quanti specificati dalla dimensione espressione.

Quindi, in breve, qualsiasi parametro di funzione dichiarato come T a[]o T a[N]viene trattato come se fosse dichiarato T *a.

Allora, perché i parametri dell'array vengono trattati come se fossero stati dichiarati come puntatori? Ecco perché:

6.3.2.1 Valori, array e designatori di funzione
...
3 Tranne quando è l'operando sizeofdell'operatore o dell'operatore unario &, o è una stringa letterale usata per inizializzare un array, un'espressione che ha tipo '' array di tipo ' 'viene convertito in un'espressione con tipo "puntatore a tipo " "che punta all'elemento iniziale dell'oggetto array e non è un lvalue. Se l'oggetto array ha una classe di archiviazione del registro, il comportamento non è definito.

Dato il seguente codice:

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

Nella chiamata a foo, l'espressione di matrice arrnon è un operando di sizeofo &, quindi il suo tipo viene convertito implicitamente da "matrice di 10 elementi di int" a "puntatore a int" secondo 6.2.3.1/3. Pertanto, fooriceverà un valore di puntatore, anziché un valore di matrice.

A causa del 6.7.5.3/7, puoi scrivere foocome

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

ma sarà interpretato come

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

Pertanto, le due forme sono identiche.

L'ultima frase in 6.7.5.3/7 è stata introdotta con C99, e in pratica significa che se hai una dichiarazione di parametro come

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

il parametro effettivo corrispondente a adeve essere un array con almeno 10 elementi.


1
C'è una differenza quando si utilizzano compilatori MSVC C ++ (almeno alcuni più vecchi), a causa del fatto che il compilatore modifica erroneamente il nome della funzione in modo diverso nei due casi (pur riconoscendo che sono uguali altrimenti), con conseguenti problemi di collegamento. Vedi la segnalazione di bug "Non risolverò" qui connect.microsoft.com/VisualStudio/feedback/details/326874/…
greggo

29

La differenza è puramente sintattica. In C, quando la notazione di matrice viene utilizzata per un parametro di funzione, viene automaticamente trasformata in una dichiarazione di puntatore.


1
@Kaushik: Anche se sono gli stessi in questo caso, tieni presente che non sono gli stessi nel caso generale
BlueRaja - Danny Pflughoeft

@BlueRaja: sì, è una delle insidie ​​di C.La dichiarazione dei parametri della funzione è molto simile alla dichiarazione delle variabili locali, ma ci sono alcune sottili differenze (come questa trasformazione automatica da matrice a puntatore) che sono incline a mordere il programmatore incauto.
Thomas Pornin

0

No, non c'è differenza tra loro. Per testare ho scritto questo codice C nel compilatore Dev C ++ (mingw):

#include <stdio.h>

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

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

Quando disassemblo la funzione principale in .exe di entrambe le versioni chiamanti del file binario in IDA ottengo esattamente lo stesso codice assembly come di seguito:

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

Quindi non c'è differenza tra le due versioni di questa chiamata, almeno il compilatore le minaccia allo stesso modo.


18
Siamo spiacenti, ma questo dimostra solo che alcune versioni di gcc generano lo stesso assembly su x86 per entrambi. Risposta corretta, spiegazione sbagliata.
Lambdapower
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.