Perbedaan antara melewatkan array dan pointer array ke dalam fungsi di C

110

Apa perbedaan antara kedua fungsi di C?

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

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

Jika saya memanggil fungsi pada array yang sangat panjang, akankah kedua fungsi ini berperilaku berbeda, apakah mereka akan mengambil lebih banyak ruang di stack?

Kaushik Shankar
sumber

Jawaban:

114

Pertama, beberapa standar :

6.7.5.3 Deklarator fungsi (termasuk prototipe)
...
7 Deklarasi parameter sebagai '' array tipe '' harus disesuaikan ke '' penunjuk ke tipe yang memenuhi syarat '', di mana kualifikasi tipe (jika ada) adalah yang ditentukan dalam [dan ]dari derivasi tipe array. Jika kata kunci staticjuga muncul dalam [dan] dari derivasi tipe array, maka untuk setiap panggilan ke fungsi, nilai argumen aktual yang sesuai harus memberikan akses ke elemen pertama dari sebuah array dengan setidaknya sebanyak elemen yang ditentukan oleh ukurannya ekspresi.

Jadi, singkatnya, parameter fungsi apa pun yang dideklarasikan sebagai T a[]atau T a[N]diperlakukan seolah-olah dideklarasikan T *a.

Jadi, mengapa parameter array diperlakukan seolah-olah dideklarasikan sebagai pointer? Inilah alasannya:

6.3.2.1 Lvalues, array, dan function designators
...
3 Kecuali jika itu adalah operan dari sizeofoperator atau operator unary &, atau adalah string literal yang digunakan untuk menginisialisasi sebuah array, sebuah ekspresi yang memiliki tipe '' tipe array ' 'diubah menjadi ekspresi dengan tipe' 'pointer to type ' 'yang menunjuk ke elemen awal objek array dan bukan nilai l. Jika objek array memiliki kelas penyimpanan register, perilakunya tidak ditentukan.

Diberikan kode berikut:

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

Dalam panggilan ke foo, ekspresi larikarr bukanlah operand dari salah satu sizeofatau &, jadi tipenya secara implisit diubah dari "array 10-elemen int" menjadi "pointer ke int" menurut 6.2.3.1/3. Dengan demikian, fooakan menerima nilai pointer, bukan nilai array.

Karena 6.7.5.3/7, Anda dapat menulis foosebagai

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

tapi itu akan diartikan sebagai

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

Jadi, kedua bentuk itu identik.

Kalimat terakhir di 6.7.5.3/7 diperkenalkan dengan C99, dan pada dasarnya berarti jika Anda memiliki deklarasi parameter seperti

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

parameter aktual yang sesuai dengan aharus berupa larik dengan setidaknya 10 elemen.

John Bode
sumber
1
Ada perbedaan saat menggunakan (setidaknya beberapa yang lebih lama) kompiler MSVC C ++, karena kompiler salah mengotak-atik nama fungsi secara berbeda dalam dua kasus (sementara mengenali bahwa mereka sama sebaliknya), mengakibatkan masalah tautan. Lihat laporan bug "Tidak akan diperbaiki" di sini connect.microsoft.com/VisualStudio/feedback/details/326874/…
greggo
29

Perbedaannya murni sintaksis. Di C, ketika notasi array digunakan untuk parameter fungsi, itu secara otomatis diubah menjadi deklarasi pointer.

Thomas Pornin
sumber
1
@Kaushik: Meskipun dalam kasus ini sama, perlu diingat bahwa keduanya tidak sama dalam kasus umum
BlueRaja - Danny Pflughoeft
@BlueRaja: ya, ini adalah salah satu perangkap C. Deklarasi parameter fungsi sangat mirip dengan deklarasi variabel lokal, tetapi ada beberapa perbedaan halus (seperti transformasi otomatis array-ke-pointer) yang cenderung menggigit programmer yang tidak waspada.
Thomas Pornin
0

Tidak, tidak ada perbedaan di antara keduanya. Untuk menguji saya menulis kode C ini di Dev C ++ (mingw) compiler:

#include <stdio.h>

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

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

Ketika saya membongkar fungsi utama di .exe dari kedua versi panggilan file biner di IDA, saya mendapatkan kode assembly yang persis sama seperti di bawah ini:

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

Jadi tidak ada perbedaan antara kedua versi panggilan ini, setidaknya kompilator mengancamnya secara setara.

caltuntas
sumber
18
Maaf, tapi ini hanya membuktikan bahwa beberapa versi gcc menghasilkan assembly yang sama di x86 untuk keduanya. Jawaban yang benar, penjelasan yang salah.
lambdapower