Sự khác biệt giữa chuyển mảng và con trỏ mảng vào hàm trong C


110

Sự khác biệt giữa hai chức năng trong C là gì?

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

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

Nếu tôi gọi các hàm trên một mảng dài đáng kể, liệu hai hàm này có hoạt động khác nhau không, chúng có chiếm nhiều không gian hơn trên ngăn xếp không?

Câu trả lời:


114

Đầu tiên, một số tiêu chuẩn :

6.7.5.3 Bộ khai báo hàm (bao gồm cả nguyên mẫu)
...
7 Khai báo một tham số là '' mảng kiểu '' sẽ được điều chỉnh thành '' con trỏ đủ điều kiện để loại '', trong đó các định nghĩa kiểu (nếu có) là những giá trị được chỉ định trong []của dẫn xuất kiểu mảng. Nếu từ khóa staticcũng xuất hiện trong []của dẫn xuất kiểu mảng, thì đối với mỗi lệnh gọi hàm, giá trị của đối số thực tế tương ứng sẽ cung cấp quyền truy cập vào phần tử đầu tiên của một mảng có ít nhất bao nhiêu phần tử như được chỉ định bởi kích thước biểu hiện.

Vì vậy, trong ngắn hạn, bất kỳ tham số hàm nào được khai báo T a[]hoặc T a[N]được coi như thể nó đã được khai báo T *a.

Vì vậy, tại sao các tham số mảng được xử lý như thể chúng được khai báo dưới dạng con trỏ? Đây là lý do tại sao:

6.3.2.1 Giá trị, mảng và ký hiệu hàm
...
3 Ngoại trừ khi nó là toán hạng của sizeoftoán tử hoặc toán &tử một ngôi, hoặc là một chuỗi ký tự được sử dụng để khởi tạo một mảng, một biểu thức có kiểu '' mảng kiểu ' 'được chuyển đổi thành một biểu thức có kiểu' 'con trỏ để loại ' 'trỏ đến phần tử ban đầu của đối tượng mảng và không phải là một giá trị. Nếu đối tượng mảng có lớp lưu trữ đăng ký, hành vi là không xác định.

Cho đoạn mã sau:

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

Trong lệnh gọi tới foo, biểu thức mảng arrkhông phải là toán hạng của một trong hai sizeofhoặc &, vì vậy kiểu của nó được chuyển đổi ngầm định từ "mảng 10 phần tử của int" thành "con trỏ tới int" theo 6.2.3.1/3. Do đó, foosẽ nhận một giá trị con trỏ, thay vì một giá trị mảng.

Vì 6.7.5.3/7, bạn có thể viết foo

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

nhưng nó sẽ được hiểu là

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

Như vậy, hai hình thức giống hệt nhau.

Câu cuối cùng trong 6.7.5.3/7 được giới thiệu với C99 và về cơ bản có nghĩa là nếu bạn có một khai báo tham số như

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

tham số thực tế tương ứng với aphải là một mảng có ít nhất 10 phần tử.


1
Có một sự khác biệt khi sử dụng (ít nhất là một số trình biên dịch MSVC C ++ cũ hơn), do trình biên dịch đặt sai tên hàm khác nhau trong hai trường hợp (trong khi thừa nhận rằng chúng giống nhau về mặt khác), dẫn đến sự cố liên kết. Xem báo cáo lỗi "Sẽ không sửa được" tại đây connect.microsoft.com/VisualStudio/feedback/details/326874/…
greggo 12/1213

29

Sự khác biệt hoàn toàn là cú pháp. Trong C, khi ký hiệu mảng được sử dụng cho một tham số hàm, nó sẽ tự động được chuyển thành khai báo con trỏ.


1
@Kaushik: Mặc dù họ đều giống nhau trong trường hợp này, hãy ghi nhớ rằng họ không giống nhau trong trường hợp chung
BlueRaja - Danny Pflughoeft

@BlueRaja: vâng, đó là một trong những cạm bẫy của C. Việc khai báo các tham số hàm rất giống với việc khai báo các biến cục bộ, nhưng có một số khác biệt nhỏ (chẳng hạn như biến đổi tự động mảng thành con trỏ này) dễ bị cắn các lập trình viên không cẩn thận.
Thomas Pornin

0

Không, không có sự khác biệt giữa chúng. Để kiểm tra, tôi đã viết mã C này trong trình biên dịch Dev C ++ (mingw):

#include <stdio.h>

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

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

Khi tôi tháo rời hàm main trong .exe của cả hai phiên bản đang gọi của tệp nhị phân trong IDA, tôi nhận được chính xác cùng một mã lắp ráp như bên dưới:

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

Vì vậy, không có sự khác biệt giữa hai phiên bản của cuộc gọi này, ít nhất là trình biên dịch đe dọa chúng như nhau.


18
Xin lỗi, nhưng điều này chỉ chứng minh rằng một số phiên bản gcc tạo ra cùng một assembly trên x86 cho cả hai. Câu trả lời đúng, lời giải thích sai.
lambdapower
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.