2011-01-05 46 views
34

Sự hiểu biết của tôi là mảng chỉ đơn giản là con trỏ liên tục đến một chuỗi các giá trị, và khi bạn khai báo một mảng trong C, bạn đã khai báo một con trỏ và phân bổ không gian cho chuỗi nó trỏ đến.Trong C, là các con trỏ mảng hay được sử dụng như con trỏ?

Nhưng điều này confuses me: đoạn mã sau:

char y[20]; 
char *z = y; 

printf("y size is %lu\n", sizeof(y)); 
printf("y is %p\n", y); 
printf("z size is %lu\n", sizeof(z)); 
printf("z is %p\n", z); 

khi biên soạn với Apple GCC cung cấp cho các kết quả sau:

y size is 20 
y is 0x7fff5fbff930 
z size is 8 
z is 0x7fff5fbff930 

(máy của tôi là 64 bit, con trỏ dài 8 byte) .

Nếu 'y' là một con trỏ không đổi, tại sao nó có kích thước 20, giống như chuỗi giá trị mà nó trỏ đến? Là tên biến 'y' được thay thế bởi một địa chỉ bộ nhớ trong thời gian biên dịch bất cứ khi nào nó là appropiate? Là mảng, sau đó, một số loại đường cú pháp trong C mà chỉ được dịch sang công cụ con trỏ khi biên dịch?

+2

có thể trùng lặp của câu hỏi từ C++ của trang web - faq: [Tên mảng có phải là con trỏ trong C?] (Http://stackoverflow.com/questions/1641957/is-array-name-a-pointer-in- c) –

Trả lời

49

Đây là ngôn ngữ chính xác từ các tiêu chuẩn C (n1256):

6.3.2.1 lvalues, mảng, và chức năng định danh
...
3 Trừ khi nó là toán hạng của sizeof toán tử hoặc toán tử & đơn nhất hoặc là chuỗi được sử dụng để khởi tạo mảng, biểu thức có loại '' mảng loại '' được chuyển thành biểu thức có loại '' thành loại ’Trỏ đến các yếu tố ban đầu của đối tượng mảng và không phải là một giá trị trái. Nếu đối tượng mảng có lớp lưu trữ đăng ký, hành vi là không xác định.

Điều quan trọng cần nhớ ở đây là có sự khác biệt giữa một đối tượng (về C, có nghĩa là một cái gì đó chiếm bộ nhớ) và biểu dùng để chỉ đối tượng đó.

Khi bạn khai báo một mảng như

int a[10]; 

các đối tượng định bởi biểu thức a là một mảng (ví dụ:, một khối bộ nhớ liền kề đủ lớn để chứa 10 int giá trị) và loại biểu thức a là "mảng 10 phần tử của int" hoặc int [10]. Nếu biểu thức biểu thịa xuất hiện trong một ngữ cảnh khác với toán hạng của các toán tử sizeof hoặc &, thì loại của nó được chuyển đổi hoàn toàn thành int * và giá trị của nó là địa chỉ của phần tử đầu tiên.

Trong trường hợp toán tử sizeof, nếu toán hạng là biểu thức loại T [N], thì kết quả là số byte trong đối tượng mảng, không phải trong con trỏ đến đối tượng đó: N * sizeof T.

Trong trường hợp của toán tử &, giá trị là địa chỉ của mảng, tương tự như địa chỉ của phần tử đầu tiên của mảng, nhưng loại loại của biểu thức khác nhau: T a[N];, loại biểu thức &aT (*)[N] hoặc con trỏ đến phần tử N của T. Giá trị giống với a hoặc &a[0] (địa chỉ của mảng giống với địa chỉ của phần tử đầu tiên trong mảng), nhưng sự khác biệt trong các loại vấn đề. Ví dụ, với mã

int a[10]; 
int *p = a; 
int (*ap)[10] = &a; 

printf("p = %p, ap = %p\n", (void *) p, (void *) ap); 
p++; 
ap++; 
printf("p = %p, ap = %p\n", (void *) p, (void *) ap); 

bạn sẽ thấy đầu ra vào thứ tự của

p = 0xbff11e58, ap = 0xbff11e58 
p = 0xbff11e5c, ap = 0xbff11e80 

IOW, tiến p thêm sizeof int (4) với giá trị ban đầu, trong khi tiến ap thêm 10 * sizeof int (40).

Nhiều ngôn ngữ tiêu chuẩn:

6.5.2.1 subscripting Mảng

chế

1 Một trong những biểu thức có trách nhiệm gõ '' con trỏ đến đối tượng loại '', khác biểu thức phải có loại số nguyên và kết quả có loại '' loại ''.

Semantics

2 Một biểu thức postfix tiếp theo là một biểu hiện trong dấu ngoặc vuông [] là một định subscripted của một phần tử của một đối tượng mảng. Định nghĩa của toán tử số đăng ký []E1[E2] giống hệt với (*((E1)+(E2))). Do quy tắc chuyển đổi áp dụng cho toán tử nhị phân +, nếu E1 là đối tượng mảng (tương đương, con trỏ đến phần tử ban đầu của đối tượng mảng) và E2 là số nguyên, E1[E2] chỉ định thành phần E2 -th E1 từ số không).

Vì vậy, khi bạn chỉ định một biểu thức mảng, điều xảy ra dưới mui xe là bù đắp từ địa chỉ của phần tử đầu tiên trong mảng được tính toán và kết quả sẽ bị hủy.Khái niệm

a[i] = 10; 

tương đương với

*((a)+(i)) = 10; 

tương đương với

*((i)+(a)) = 10; 

tương đương với

i[a] = 10; 

Vâng, mảng subscripting trong C là giao hoán; vì tình yêu của Thiên Chúa, không bao giờ làm điều này trong mã sản xuất.

Kể từ mảng subscripting được định nghĩa về hoạt động con trỏ, bạn có thể áp dụng các nhà điều hành subscript để biểu thức kiểu con trỏ cũng như kiểu mảng:

int *p = malloc(sizeof *p * 10); 
int i; 
for (i = 0; i < 10; i++) 
    p[i] = some_initial_value(); 

Dưới đây là một bảng tiện dụng để ghi nhớ một số các khái niệm:

 
Declaration: T a[N]; 

Expression Type Converts to  Value 
---------- ---- ------------ ----- 
     a T [N] T *    Address of the first element in a; 
             identical to writing &a[0] 
     &a T (*)[N]    Address of the array; value is the same 
             as above, but the type is different 
    sizeof a size_t     Number of bytes contained in the array 
             object (N * sizeof T) 
     *a T      Value at a[0] 
     a[i] T      Value at a[i] 
    &a[i] T *      Address of a[i] 

Declaration: T a[N][M]; 

Expression  Type  Converts to  Value 
----------  ----  ------------ ----- 
      a T [N][M] T (*)[M]  Address of the first subarray (&a[0]) 
     &a T (*)[N][M]     Address of the array (same value as 
              above, but different type) 
    sizeof a size_t      Number of bytes contained in the 
              array object (N * M * sizeof T) 
     *a T [M]  T *    Value of a[0], which is the address 
              of the first element of the first subarray 
              (same as &a[0][0]) 
     a[i] T [M]  T *    Value of a[i], which is the address 
              of the first element of the i'th subarray 
     &a[i] T (*)[M]     Address of the i-th subarray; same value as 
              above, but different type 
sizeof a[i] size_t      Number of bytes contained in the i'th subarray 
              object (M * sizeof T) 
     *a[i] T       Value of the first element of the i'th 
              subarray (a[i][0]) 
    a[i][j] T       Value at a[i][j] 
    &a[i][j] T *       Address of a[i][j] 

Declaration: T a[N][M][O]; 

Expression  Type    Converts to 
----------  ----    ----------- 
     a  T [N][M][O]  T (*)[M][O] 
     &a  T (*)[N][M][O] 
     *a  T [M][O]   T (*)[O] 
     a[i]  T [M][O]   T (*)[O] 
    &a[i]  T (*)[M][O] 
    *a[i]  T [O]   T * 
    a[i][j]  T [O]   T * 
    &a[i][j]  T (*)[O] 
    *a[i][j]  T 
a[i][j][k]  T 

Từ đây, mẫu cho mảng chiều cao phải rõ ràng.

Vì vậy, tóm lại: mảng không phải là con trỏ. Trong hầu hết các ngữ cảnh, biểu thức mảng được chuyển đổi thành loại con trỏ.

23

Mảng không phải là con trỏ, mặc dù trong hầu hết các biểu thức, tên mảng đánh giá thành con trỏ tới thành phần đầu tiên của mảng. Vì vậy, nó rất, rất dễ sử dụng một tên mảng như một con trỏ. Bạn sẽ thường thấy thuật ngữ 'phân rã' được sử dụng để mô tả điều này, như trong "mảng bị phân rã thành con trỏ".

Một ngoại lệ là toán hạng cho toán tử sizeof, trong đó kết quả là kích thước của mảng (tính bằng byte chứ không phải phần tử).

Một cặp vợ chồng thêm về các vấn đề liên quan đến điều này:

Một tham số mảng tới một hàm là một hư cấu - trình biên dịch thực sự vượt qua một con trỏ đồng bằng (điều này không áp dụng thông số để tham khảo-to-mảng trong C++) , do đó bạn không thể xác định kích thước thực của mảng được chuyển đến hàm - bạn phải chuyển thông tin đó theo cách khác (có thể sử dụng tham số bổ sung rõ ràng hoặc sử dụng phần tử sentinel - như chuỗi C)

Ngoài ra, thành ngữ phổ biến để lấy số phần tử trong một mảng là sử dụng macro như:

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0])) 

Điều này có vấn đề khi chấp nhận hoặc là tên mảng, nơi nó sẽ hoạt động hoặc con trỏ, nơi nó sẽ cho kết quả vô nghĩa mà không có cảnh báo từ trình biên dịch. Có tồn tại các phiên bản an toàn hơn của macro (đặc biệt là cho C++) sẽ tạo ra một cảnh báo hoặc lỗi khi nó được sử dụng với một con trỏ thay vì một mảng. Xem các mục SO sau:


Lưu ý: C99 Vlas (mảng chiều dài thay đổi) có thể không làm theo tất cả các quy tắc này (đặc biệt là họ có thể được thông qua như các tham số với kích thước mảng được gọi bởi hàm được gọi). Tôi có ít kinh nghiệm với VLAs, và theo như tôi biết chúng không được sử dụng rộng rãi. Tuy nhiên, tôi muốn chỉ ra rằng các cuộc thảo luận ở trên có thể áp dụng khác nhau cho VLAs.

+0

vì vậy ngoại lệ sizeof đã được thực hiện bởi vì nó rất hữu ích ... tôi không biết có thực sự là một cách để biết kích thước của một mảng! (mặc dù nó vẫn không hữu ích vì nó chỉ tìm kích thước của mảng với kích thước cố định, nhưng tôi đoán nó tốt hơn việc xác định nhiều hằng số cho cùng mục đích) –

6

sizeof được đánh giá tại thời gian biên dịch và trình biên dịch biết liệu toán hạng là một mảng hay một con trỏ. Đối với mảng, nó cho số byte chiếm bởi mảng. Mảng của bạn là char[] (và sizeof(char) là 1), do đó sizeof xảy ra để cung cấp cho bạn số lượng phần tử.Để có được số phần tử trong trường hợp tổng quát, một thành ngữ phổ biến là (vào đây để int):

int y[20]; 
printf("number of elements in y is %lu\n", sizeof(y)/sizeof(int)); 

Đối với con trỏ sizeof cho biết số byte chiếm đóng bởi các loại con trỏ thô.

-1

Nếu 'y' là một con trỏ hằng, tại sao nó có kích thước 20, giống như chuỗi các giá trị nó trỏ tới ?

z là địa chỉ của biến và sẽ luôn trả về 8 cho máy của bạn. Bạn cần sử dụng con trỏ dereference (&) để lấy nội dung của một biến.

EDIT: Một sự tương phản tốt giữa hai: http://www.cs.cf.ac.uk/Dave/C/node10.html

+0

Anh ta hỏi về y, và bạn đang trả lời về z, điều gây nhầm lẫn . Đó là lý do tại sao z có một kích thước của 8. Nó không rõ ràng cho OP tại sao y không; mà bạn không trả lời. Bên cạnh đó, '&' là toán tử địa chỉ trong C; toán tử dereference là '*'. –

1

Trong

char hello[] = "hello there" 
int i; 

char* hello = "hello there"; 
int i; 

Trong trường hợp đầu tiên (chiết khấu alignment) 12 byte sẽ được lưu trữ cho xin chào với không gian được phân bổ được khởi tạo là xin chào trong khi ở số thứ hai xin chào ở đó được lưu trữ ở nơi khác (có thể là không gian tĩnh) và hello được khởi tạo để trỏ đến chuỗi đã cho.

hello[2] cũng như *(hello + 2) sẽ trả về 'e' trong cả hai trường hợp.

Các vấn đề liên quan