Là một tên mảng một con trỏ?


203

Là một tên của một mảng một con trỏ trong C? Nếu không, sự khác biệt giữa tên của một mảng và một biến con trỏ là gì?


4
Không. Nhưng mảng là như nhau & mảng [0]

36
@pst: &array[0]mang lại một con trỏ, không phải là một mảng;)
jalf

28
@Nava (và pst): mảng& mảng [0] không thực sự giống nhau. Case in point: sizeof (mảng)sizeof (& mảng [0]) cho kết quả khác nhau.
Thomas Padron-McCarthy

1
@Thomas đồng ý, nhưng về mặt con trỏ, khi bạn quy định mảng và & mảng [0], chúng tạo ra cùng một giá trị của mảng [0] .ie * mảng == mảng [0]. Không ai có nghĩa là hai con trỏ này giống nhau, nhưng trong trường hợp cụ thể này (chỉ vào phần tử đầu tiên), bạn có thể sử dụng tên của mảng.
Nava Carmon

1
Những điều này cũng có thể giúp bạn hiểu: stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Dinah

Câu trả lời:


255

Một mảng là một mảng và một con trỏ là một con trỏ, nhưng trong hầu hết các trường hợp, tên mảng được chuyển đổi thành con trỏ. Một thuật ngữ thường được sử dụng là chúng phân rã thành con trỏ.

Đây là một mảng:

int a[7];

a chứa không gian cho bảy số nguyên và bạn có thể đặt một giá trị vào một trong số chúng bằng một phép gán, như sau:

a[3] = 9;

Đây là một con trỏ:

int *p;

pkhông chứa bất kỳ khoảng trắng nào cho số nguyên, nhưng nó có thể trỏ đến khoảng trắng cho một số nguyên. Ví dụ, chúng ta có thể đặt nó để trỏ đến một trong những vị trí trong mảng a, chẳng hạn như vị trí đầu tiên:

p = &a[0];

Điều có thể gây nhầm lẫn là bạn cũng có thể viết điều này:

p = a;

Điều này không sao chép nội dung của mảng avào con trỏ p(bất cứ điều gì có nghĩa). Thay vào đó, tên mảng ađược chuyển đổi thành một con trỏ thành phần tử đầu tiên của nó. Vì vậy, nhiệm vụ đó làm giống như trước đó.

Bây giờ bạn có thể sử dụng ptheo cách tương tự như một mảng:

p[3] = 17;

Lý do mà điều này hoạt động là toán tử hội nghị mảng trong C [ ], được định nghĩa theo các con trỏ. x[y]có nghĩa là: bắt đầu với con trỏ x, bước ycác phần tử chuyển tiếp sau những gì con trỏ trỏ tới, và sau đó lấy bất cứ thứ gì ở đó. Sử dụng cú pháp số học con trỏ, x[y]cũng có thể được viết là *(x+y).

Đối với điều này để làm việc với một mảng bình thường, chẳng hạn như chúng tôi a, tên aa[3]trước tiên phải được chuyển đổi sang một con trỏ (tới phần tử đầu tiên trong a). Sau đó, chúng tôi bước 3 yếu tố về phía trước, và lấy bất cứ thứ gì ở đó. Nói cách khác: lấy phần tử ở vị trí 3 trong mảng. (Đây là phần tử thứ tư trong mảng, vì phần tử đầu tiên được đánh số 0.)

Vì vậy, tóm lại, tên mảng trong chương trình C (trong hầu hết các trường hợp) được chuyển đổi thành con trỏ. Một ngoại lệ là khi chúng ta sử dụng sizeoftoán tử trên một mảng. Nếu ađược chuyển đổi thành một con trỏ trong ngữ cảnh này, sizeof asẽ cho kích thước của một con trỏ chứ không phải của mảng thực tế, điều này sẽ khá vô dụng, vì vậy trong trường hợp đó acó nghĩa là chính mảng đó.


5
Một chuyển đổi tự động tương tự được áp dụng cho các con trỏ hàm - cả hai functionpointer()(*functionpointer)()có nghĩa là cùng một điều, đủ kỳ lạ.
Carl Norum

3
Anh ta không hỏi liệu các mảng và con trỏ có giống nhau không, nhưng nếu tên của một mảng là một con trỏ
Ricardo Amores

32
Một tên mảng không phải là một con trỏ. Đây là một định danh cho một biến của mảng kiểu, có một chuyển đổi ngầm định thành con trỏ của kiểu phần tử.
Pavel Minaev

29
Ngoài ra, ngoài sizeof()bối cảnh khác trong đó không có mảng -> phân rã con trỏ là toán tử &- trong ví dụ của bạn ở trên, &asẽ là một con trỏ tới một mảng 7 int, không phải là một con trỏ tới một mảng int; nghĩa là, kiểu của nó sẽ là int(*)[7], không hoàn toàn có thể chuyển đổi thành int*. Theo cách này, các hàm thực sự có thể đưa con trỏ tới các mảng có kích thước cụ thể và thực thi các hạn chế thông qua hệ thống loại.
Pavel Minaev

3
@ onmyway133, kiểm tra ở đây để được giải thích ngắn gọn và trích dẫn thêm.
Carl Norum

36

Khi một mảng được sử dụng làm giá trị, tên của nó đại diện cho địa chỉ của phần tử đầu tiên.
Khi một mảng không được sử dụng làm giá trị, tên của nó đại diện cho toàn bộ mảng.

int arr[7];

/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */

/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */

20

Nếu một biểu hiện của kiểu mảng (chẳng hạn như tên mảng) xuất hiện trong một biểu thức lớn hơn và nó không phải là toán hạng của một trong hai &hoặc sizeofkhai thác, sau đó loại của biểu thức mảng được chuyển đổi từ "mảng N phần tử của T" để "Con trỏ tới T" và giá trị của biểu thức là địa chỉ của phần tử đầu tiên trong mảng.

Nói tóm lại, tên mảng không phải là một con trỏ, nhưng trong hầu hết các bối cảnh, nó được xử lý như thể nó là một con trỏ.

Biên tập

Trả lời câu hỏi trong bình luận:

Nếu tôi sử dụng sizeof, tôi có đếm kích thước chỉ các phần tử của mảng không? Sau đó, mảng Head head cũng chiếm không gian với thông tin về chiều dài và con trỏ (và điều này có nghĩa là nó chiếm nhiều không gian hơn so với một con trỏ bình thường)?

Khi bạn tạo một mảng, không gian duy nhất được phân bổ là không gian cho chính các phần tử; không lưu trữ được cụ thể hóa cho một con trỏ riêng biệt hoặc bất kỳ siêu dữ liệu nào. Được

char a[10];

những gì bạn nhận được trong bộ nhớ là

   +---+
a: |   | a[0]
   +---+ 
   |   | a[1]
   +---+
   |   | a[2]
   +---+
    ...
   +---+
   |   | a[9]
   +---+

Các biểu a đề cập đến toàn bộ mảng, nhưng không có đối tượng a riêng biệt từ các phần tử mảng mình. Do đó, sizeof acung cấp cho bạn kích thước (tính bằng byte) của toàn bộ mảng. Biểu thức &acung cấp cho bạn địa chỉ của mảng, giống như địa chỉ của phần tử đầu tiên . Sự khác biệt giữa &a&a[0]là loại kết quả 1 - char (*)[10]trong trường hợp đầu tiên và char *trong trường hợp thứ hai.

Trường hợp mọi thứ trở nên kỳ lạ là khi bạn muốn truy cập các phần tử riêng lẻ - biểu thức a[i]được xác định là kết quả của *(a + i)- được đưa ra một giá trị địa chỉ a, iphần tử bù ( không phải byte ) từ địa chỉ đó và kết quả lại.

Vấn đề akhông phải là con trỏ hay địa chỉ - đó là toàn bộ đối tượng mảng. Do đó, quy tắc trong C là bất cứ khi nào trình biên dịch nhìn thấy một biểu thức của kiểu mảng (chẳng hạn như acó kiểu char [10]) biểu thức đó không phải là toán hạng của sizeoftoán tử hoặc toán tử đơn nguyên &, loại biểu thức đó được chuyển đổi ("decays") đến một kiểu con trỏ ( char *) và giá trị của biểu thức là địa chỉ của phần tử đầu tiên của mảng. Do đó, biểu thức a có cùng loại và giá trị với biểu thức &a[0](và bằng phần mở rộng, biểu thức *acó cùng loại và giá trị với biểu thức a[0]).

C được bắt nguồn từ một ngôn ngữ trước đó được gọi là B, và trong B a một đối tượng con trỏ tách biệt với các phần tử mảng a[0], a[1]vv Ritchie muốn giữ ngữ nghĩa mảng B, nhưng ông không muốn gây rối với lưu trữ các đối tượng con trỏ riêng biệt. Vì vậy, anh đã thoát khỏi nó. Thay vào đó, trình biên dịch sẽ chuyển đổi các biểu thức mảng thành các biểu thức con trỏ trong quá trình dịch khi cần thiết.

Hãy nhớ rằng tôi đã nói các mảng không lưu trữ bất kỳ siêu dữ liệu nào về kích thước của chúng. Ngay khi biểu thức mảng đó "phân rã" thành một con trỏ, tất cả những gì bạn có là một con trỏ tới một phần tử. Phần tử đó có thể là phần đầu tiên của chuỗi các phần tử hoặc nó có thể là một đối tượng. Không có cách nào để biết dựa trên chính con trỏ.

Khi bạn chuyển một biểu thức mảng cho một hàm, tất cả các hàm nhận được là một con trỏ tới phần tử đầu tiên - nó không biết mảng đó lớn đến mức nào (đây là lý do tại sao getshàm đó là một mối đe dọa như vậy và cuối cùng đã bị xóa khỏi thư viện). Để hàm biết có bao nhiêu phần tử của mảng, bạn phải sử dụng giá trị sentinel (chẳng hạn như dấu kết thúc 0 trong chuỗi C) hoặc bạn phải truyền số lượng phần tử làm tham số riêng.


  1. Mà * có thể * ảnh hưởng đến cách giá trị địa chỉ được diễn giải - tùy thuộc vào máy.

Đã tìm kiếm khá lâu cho câu trả lời này. Cảm ơn bạn! Và nếu bạn biết, bạn có thể nói thêm một chút về biểu thức mảng là gì không. Nếu tôi sử dụng sizeof, tôi có đếm kích thước chỉ các phần tử của mảng không? Sau đó, mảng Head head cũng chiếm không gian với thông tin về chiều dài và con trỏ (và điều này có nghĩa là nó chiếm nhiều không gian hơn so với một con trỏ bình thường)?
Andriy Dmytruk

Và một điều nữa. Một mảng có độ dài 5 là kiểu int [5]. Vậy đó là từ nơi chúng ta biết độ dài khi chúng ta gọi sizeof (mảng) - từ loại của nó? Và điều này có nghĩa là các mảng có độ dài khác nhau giống như các loại hằng khác nhau?
Andriy Dmytruk

@AndriyDmytruk: sizeoflà một toán tử và nó ước tính số byte trong toán hạng (biểu thức biểu thị một đối tượng hoặc tên kiểu trong ngoặc đơn). Vì vậy, đối với một mảng, sizeofước tính số lượng phần tử nhân với số byte trong một phần tử. Nếu an intrộng 4 byte thì mảng 5 phần tử intchiếm 20 byte.
John Bode

Có phải nhà điều hành [ ]cũng không đặc biệt? Ví dụ, int a[2][3];sau đó x = a[1][2];, mặc dù nó có thể được viết lại thành x = *( *(a+1) + 2 );, ở đây akhông được chuyển đổi thành một loại con trỏ int*(mặc dù nếu alà một đối số của hàm thì nó nên được chuyển đổi thành int*).
Stan

2
@Stan: Biểu thức acó kiểu int [2][3], "phân rã" thành kiểu int (*)[3]. Biểu thức *(a + 1)có kiểu int [3], "phân rã" thành int *. Như vậy, *(*(a + 1) + 2)sẽ có loại int. ađiểm đến các mảng 3 phần tử đầu tiên của int, a + 1điểm đến các mảng 3 phần tử thứ hai của int, *(a + 1) mảng 3 phần tử thứ hai của int, *(a + 1) + 2điểm đến yếu tố thứ ba của mảng thứ hai của int, do đó *(*(a + 1) + 2) yếu tố thứ ba của mảng thứ hai của int. Làm thế nào mà được ánh xạ tới mã máy hoàn toàn phụ thuộc vào trình biên dịch.
John Bode

5

Một mảng được khai báo như thế này

int a[10];

cấp phát bộ nhớ trong 10 intgiây. Bạn không thể sửa đổi anhưng bạn có thể thực hiện số học con trỏ với a.

Một con trỏ như thế này phân bổ bộ nhớ cho con trỏ p:

int *p;

Nó không phân bổ bất kỳ ints. Bạn có thể sửa đổi nó:

p = a;

và sử dụng các mục con mảng như bạn có thể với:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect

2
Mảng không phải luôn luôn được phân bổ trên ngăn xếp. Yhat là một chi tiết triển khai sẽ thay đổi từ trình biên dịch sang trình biên dịch. Trong hầu hết các trường hợp, mảng tĩnh hoặc toàn cục sẽ được phân bổ từ một vùng bộ nhớ khác với ngăn xếp. Mảng các loại const có thể được phân bổ từ một vùng bộ nhớ khác
Mark Bessey

1
Tôi nghĩ Grumdrig có nghĩa là nói "phân bổ 10 intgiây với thời gian lưu trữ tự động '.
Các cuộc đua nhẹ nhàng trong quỹ đạo

4

Tên mảng tự nó mang lại một vị trí bộ nhớ, vì vậy bạn có thể coi tên mảng như một con trỏ:

int a[7];

a[0] = 1976;
a[1] = 1984;

printf("memory location of a: %p", a);

printf("value at memory location %p is %d", a, *a);

Và những thứ tiện lợi khác mà bạn có thể làm với con trỏ (ví dụ: thêm / trừ phần bù), bạn cũng có thể thực hiện với một mảng:

printf("value at memory location %p is %d", a + 1, *(a + 1));

Về mặt ngôn ngữ, nếu C không phơi bày mảng chỉ là một loại "con trỏ" (về mặt giáo dục thì đó chỉ là một vị trí bộ nhớ. Nó không thể chỉ ra vị trí tùy ý trong bộ nhớ, cũng không thể được lập trình viên kiểm soát). Chúng ta luôn cần mã này:

printf("value at memory location %p is %d", &a[1], a[1]);

1

Tôi nghĩ ví dụ này làm sáng tỏ vấn đề:

#include <stdio.h>
int main()
{
        int a[3] = {9, 10, 11};
        int **b = &a;

        printf("a == &a: %d\n", a == b);
        return 0;
}

Nó biên dịch tốt (với 2 cảnh báo) trong gcc 4.9.2 và in như sau:

a == &a: 1

Giáo sư :-)

Vì vậy, kết luận là không, mảng không phải là con trỏ, nó không được lưu trong bộ nhớ (thậm chí không chỉ đọc) như một con trỏ, mặc dù có vẻ như vậy, vì bạn có thể lấy địa chỉ của nó bằng toán tử & . Nhưng - rất tiếc - toán tử đó không hoạt động :-)), dù sao đi nữa, bạn đã được cảnh báo:

p.c: In function main’:
pp.c:6:12: warning: initialization from incompatible pointer type
  int **b = &a;
            ^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
  printf("a == &a: %d\n", a == b);

C ++ từ chối mọi nỗ lực như vậy với lỗi trong thời gian biên dịch.

Biên tập:

Đây là những gì tôi muốn chứng minh:

#include <stdio.h>
int main()
{
    int a[3] = {9, 10, 11};
    void *c = a;

    void *b = &a;
    void *d = &c;

    printf("a == &a: %d\n", a == b);
    printf("c == &c: %d\n", c == d);
    return 0;
}

Mặc dù ca"trỏ" vào cùng một bộ nhớ, bạn có thể lấy địa chỉ của ccon trỏ, nhưng bạn không thể lấy địa chỉ của acon trỏ.


1
"Nó biên dịch tốt (với 2 cảnh báo)". Điều đó không ổn. Nếu bạn yêu cầu gcc biên dịch nó theo tiêu chuẩn C thích hợp bằng cách thêm -std=c11 -pedantic-errors, bạn sẽ gặp lỗi trình biên dịch khi viết mã C không hợp lệ. Lý do là vì bạn cố gắng gán a int (*)[3]cho một biến int**, đó là hai loại hoàn toàn không liên quan gì đến nhau. Vì vậy, những gì ví dụ này được cho là để chứng minh, tôi không có ý tưởng.
Lundin

Cảm ơn bạn Lundin cho ý kiến ​​của bạn. Bạn biết có nhiều tiêu chuẩn. Tôi đã cố gắng làm rõ những gì tôi có nghĩa trong chỉnh sửa. Các int **loại không phải là điểm đó, ta nên sử dụng tốt hơn void *cho việc này.
Palo

-3

Tên mảng hoạt động giống như một con trỏ và trỏ đến phần tử đầu tiên của mảng. Thí dụ:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

Cả hai câu lệnh in sẽ cung cấp chính xác cùng một đầu ra cho một máy. Trong hệ thống của tôi, nó đã cho:

0x7fff6fe40bc0

-4

Một mảng là một tập hợp các yếu tố liên tục và liên tục trong bộ nhớ. Trong C, tên của mảng là chỉ mục cho phần tử đầu tiên và áp dụng phần bù bạn có thể truy cập phần còn lại của các phần tử. Một "chỉ mục cho phần tử đầu tiên" thực sự là một con trỏ tới hướng bộ nhớ.

Sự khác biệt với các biến con trỏ là bạn không thể thay đổi vị trí mà tên của mảng đang trỏ tới, tương tự như con trỏ const (tương tự, không giống nhau. Xem bình luận của Mark). Nhưng bạn cũng không cần phải hủy đăng ký tên mảng để nhận giá trị nếu bạn sử dụng số học con trỏ:

char array = "hello wordl";
char* ptr = array;

char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'

Vì vậy, câu trả lời là "có".


1
Một tên mảng không giống như một con trỏ const. Cho: int a [10]; int * p = a; sizeof (p) và sizeof (a) không giống nhau.
Mark Bessey

1
Có sự khác biệt khác. Nói chung, tốt nhất là tuân theo thuật ngữ được sử dụng bởi Tiêu chuẩn C, mà cụ thể gọi đó là "chuyển đổi". Trích dẫn: "Ngoại trừ khi đó là toán hạng của toán tử sizeof hoặc toán tử unary & toán tử 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 biểu thức có kiểu ' 'con trỏ để gõ' 'trỏ đến phần tử ban đầu của đối tượng mảng và không phải là giá trị. Nếu đối tượng mảng có lớp lưu trữ đăng ký, hành vi không được xác định. "
Pavel Minaev

-5

Tên mảng là địa chỉ của phần tử thứ 1 của một mảng. Vì vậy, có tên mảng là một con trỏ const.

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.