ความแตกต่างระหว่างการส่งอาร์เรย์และตัวชี้อาร์เรย์ไปยังฟังก์ชันใน 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 Lvalues ​​อาร์เรย์และตัวกำหนดฟังก์ชัน
...
3 ยกเว้นเมื่อเป็นตัวถูกดำเนินการของตัวsizeofดำเนินการหรือตัวดำเนิน&การยูนารีหรือเป็นสตริงลิเทอรัลที่ใช้เริ่มต้นอาร์เรย์นิพจน์ที่มีประเภท '' array of type ' 'ถูกแปลงเป็นนิพจน์ด้วย type' 'pointer to type ' 'ที่ชี้ไปยังองค์ประกอบเริ่มต้นของวัตถุอาร์เรย์และไม่ใช่ 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 - Danny Pflughoeft

@BlueRaja: ใช่มันเป็นหนึ่งในข้อผิดพลาดของ C. การประกาศพารามิเตอร์ฟังก์ชันนั้นคล้ายกับการประกาศตัวแปรโลคัลมาก แต่มีความแตกต่างเล็กน้อย (เช่นการแปลงอาร์เรย์ต่อพอยน์เตอร์อัตโนมัติ) ซึ่ง ได้แก่ มีแนวโน้มที่จะกัดโปรแกรมเมอร์ที่ไม่ระมัดระวัง
Thomas Pornin

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.