Làm thế nào đến một địa chỉ của một mảng bằng với giá trị của nó trong C?


189

Trong bit sau của mã, giá trị con trỏ và địa chỉ con trỏ khác nhau như mong đợi.

Nhưng giá trị mảng và địa chỉ thì không!

Làm sao có thể?

Đầu ra

my_array = 0022FF00
&my_array = 0022FF00
pointer_to_array = 0022FF00
&pointer_to_array = 0022FEFC
#include <stdio.h>

int main()
{
  char my_array[100] = "some cool string";
  printf("my_array = %p\n", my_array);
  printf("&my_array = %p\n", &my_array);

  char *pointer_to_array = my_array;
  printf("pointer_to_array = %p\n", pointer_to_array);
  printf("&pointer_to_array = %p\n", &pointer_to_array);

  printf("Press ENTER to continue...\n");
  getchar();
  return 0;
}

Từ câu hỏi thường gặp của comp.lang.c: - [Vậy '`tương đương của con trỏ và mảng' 'trong C nghĩa là gì? ] ( c-faq.com/aryptr/aryptrequiv.html ) - [Vì tham chiếu mảng phân rã thành con trỏ, nếu mảng là một mảng, sự khác biệt giữa mảng và & mảng là gì? ] ( c-faq.com/aryptr/aryvsadr.html ) Hoặc đọc toàn bộ phần Mảng và Con trỏ .
jamesdlin

3
Tôi đã thêm một câu trả lời với sơ đồ cho câu hỏi này hai năm trước ở đây Điều gì sizeof(&array)trở lại?
Grijesh Chauhan

Câu trả lời:


212

Tên của một mảng thường ước tính theo địa chỉ của phần tử đầu tiên của mảng, do đó array&arraycó cùng giá trị (nhưng các loại khác nhau, do đó array+1&array+1sẽ không bằng nhau nếu mảng dài hơn 1 phần tử).

Có hai trường hợp ngoại lệ: khi tên mảng là toán hạng của sizeofhoặc unary &(address-of), tên này chỉ chính đối tượng mảng. Do đó sizeof arraycung cấp cho bạn kích thước tính bằng byte của toàn bộ mảng, không phải kích thước của một con trỏ.

Đối với một mảng được định nghĩa là T array[size], nó sẽ có kiểu T *. Khi / nếu bạn tăng nó, bạn sẽ đến phần tử tiếp theo trong mảng.

&arrayước tính cho cùng một địa chỉ, nhưng được đưa ra cùng một định nghĩa, nó tạo ra một con trỏ kiểu T(*)[size]- tức là, nó là một con trỏ tới một mảng, không phải là một phần tử. Nếu bạn tăng con trỏ này, nó sẽ thêm kích thước của toàn bộ mảng, không phải kích thước của một phần tử. Ví dụ: với mã như thế này:

char array[16];
printf("%p\t%p", (void*)&array, (void*)(&array+1));

Chúng ta có thể mong đợi con trỏ thứ hai lớn hơn con trỏ thứ nhất 16 (vì đó là một mảng 16 char). Vì% p thường chuyển đổi các con trỏ theo hệ thập lục phân, nên nó có thể trông giống như:

0x12341000    0x12341010

3
@Alexandre: &arraylà một con trỏ tới phần tử đầu tiên của mảng, trong đó như arrayđề cập đến toàn bộ mảng. Sự khác biệt cơ bản cũng có thể được quan sát bằng cách so sánh sizeof(array), với sizeof(&array). Tuy nhiên, lưu ý rằng nếu bạn chuyển arraydưới dạng đối số cho hàm, &arraythực tế chỉ có thông qua. Bạn không thể vượt qua một mảng theo giá trị trừ khi nó được gói gọn lại với a struct.
Clifford

14
@Clifford: Nếu bạn truyền mảng cho một hàm, nó sẽ phân rã thành một con trỏ đến phần tử đầu tiên của nó để nó &array[0]được truyền một cách hiệu quả , không phải &arraylà một con trỏ tới mảng. Nó có thể là một lựa chọn nit nhưng tôi nghĩ điều quan trọng là phải làm rõ; trình biên dịch sẽ cảnh báo nếu hàm có một nguyên mẫu khớp với loại con trỏ được truyền vào.
CB Bailey

2
@Jerry Coffin Ví dụ int * p = & a, nếu tôi muốn địa chỉ bộ nhớ của con trỏ int p, tôi có thể làm & p. Vì & mảng chuyển đổi thành địa chỉ của toàn bộ mảng (bắt đầu tại địa chỉ của phần tử đầu tiên). Sau đó, làm thế nào để tôi tìm địa chỉ bộ nhớ của con trỏ mảng (nơi lưu trữ địa chỉ của phần tử đầu tiên trong mảng)? Nó phải ở đâu đó trong ký ức phải không?
John Lee

2
@JohnLee: Không, không cần phải có một con trỏ tới mảng ở bất cứ đâu trong bộ nhớ. Nếu bạn tạo một con trỏ, thì bạn có thể lấy địa chỉ của nó : int *p = array; int **pp = &p;.
Jerry Coffin

3
@Clifford bình luận đầu tiên là sai, tại sao vẫn giữ nó? Tôi nghĩ rằng điều đó có thể dẫn đến sự hiểu lầm cho những người không đọc câu trả lời sau đây (@Charles).
Rick

30

Đó là bởi vì tên mảng ( my_array) khác với một con trỏ đến mảng. Nó là bí danh cho địa chỉ của một mảng và địa chỉ của nó được định nghĩa là địa chỉ của chính mảng đó.

Con trỏ là một biến C bình thường trên ngăn xếp, tuy nhiên. Do đó, bạn có thể lấy địa chỉ của nó và nhận một giá trị khác với địa chỉ mà nó chứa bên trong.

Tôi đã viết về chủ đề này ở đây - xin hãy xem.


Không nên & my_array là một hoạt động không hợp lệ vì giá trị của my_array không nằm trong ngăn xếp, chỉ có my_array [0 ... length] là? Sau đó, tất cả sẽ có ý nghĩa ...
Alexandre

@Alexandre: Thật ra tôi không chắc tại sao nó lại được cho phép.
Eli Bendersky

Bạn có thể lấy địa chỉ của bất kỳ biến nào (nếu không được đánh dấu register) bất kể thời lượng lưu trữ của nó: tĩnh, động hoặc tự động.
CB Bailey

my_array chính nó là trên ngăn xếp, bởi vì my_array toàn bộ mảng.
phê

3
my_array, khi không phải là chủ đề của toán tử &hoặc sizeoftoán tử, được ước tính cho một con trỏ tới phần tử đầu tiên của nó (tức là &my_array[0]) - nhưng my_arraybản thân nó không phải là con trỏ đó ( my_arrayvẫn là mảng). Con trỏ đó chỉ là một giá trị phù du (ví dụ int a;, được đưa ra , nó giống như vậy a + 1) - về mặt khái niệm ít nhất nó là "được tính khi cần thiết". "Giá trị" thực sự my_arraylà nội dung của toàn bộ mảng - chỉ là việc ghim xuống giá trị này trong C giống như cố gắng bắt sương mù trong bình.
phê

27

Trong C, khi bạn sử dụng tên của một mảng trong một biểu thức (bao gồm cả việc chuyển nó tới một hàm), trừ khi đó là toán hạng của toán tử address-of ( &) hoặc sizeoftoán tử, nó sẽ phân rã thành một con trỏ tới phần tử đầu tiên của nó.

Đó là, trong hầu hết các bối cảnh arraytương đương với &array[0]cả loại và giá trị.

Trong ví dụ của bạn, my_arraycó kiểu char[100]phân rã thành a char*khi bạn chuyển nó sang printf.

&my_arraycó loại char (*)[100](con trỏ tới mảng 100 char). Vì nó là toán hạng cho &, đây là một trong những trường hợp my_arraykhông phân rã ngay lập tức thành một con trỏ đến phần tử đầu tiên của nó.

Con trỏ tới mảng có cùng giá trị địa chỉ với một con trỏ tới phần tử đầu tiên của mảng vì một đối tượng mảng chỉ là một chuỗi liền kề của các phần tử của nó, nhưng một con trỏ tới một mảng có một kiểu khác với một con trỏ tới một phần tử của mảng đó. Điều này rất quan trọng khi bạn thực hiện số học con trỏ trên hai loại con trỏ.

pointer_to_array có loại char * - được khởi tạo để trỏ đến phần tử đầu tiên của mảng vì đó là phần my_arrayphân rã trong biểu thức khởi tạo - và &pointer_to_array có kiểu char **(con trỏ tới con trỏ tới a char).

Trong số này: my_array(sau khi phân rã thành char*) &my_arraypointer_to_array tất cả các điểm trực tiếp tại mảng hoặc phần tử đầu tiên của mảng và do đó có cùng giá trị địa chỉ.


3

Lý do tại sao my_array&my_array kết quả trong cùng một địa chỉ có thể dễ hiểu khi bạn nhìn vào bố cục bộ nhớ của một mảng.

Giả sử bạn có một mảng gồm 10 ký tự (thay vì 100 trong mã của bạn).

char my_array[10];

Bộ nhớ cho my_arraytrông giống như:

+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array.

Trong C / C ++, một mảng phân rã con trỏ tới phần tử đầu tiên trong một biểu thức, chẳng hạn như

printf("my_array = %p\n", my_array);

Nếu bạn kiểm tra phần tử đầu tiên của mảng nằm ở đâu, bạn sẽ thấy địa chỉ của nó giống với địa chỉ của mảng:

my_array[0]
|
v
+---+---+---+---+---+---+---+---+---+---+
|   |   |   |   |   |   |   |   |   |   |
+---+---+---+---+---+---+---+---+---+---+
^
|
Address of my_array[0].

3

Trong ngôn ngữ lập trình B, tiền thân của C, các con trỏ và số nguyên được tự do thay thế cho nhau. Hệ thống sẽ hoạt động như thể tất cả bộ nhớ là một mảng khổng lồ. Mỗi tên biến có một địa chỉ toàn cục hoặc liên quan đến ngăn xếp được liên kết với nó, đối với mỗi tên biến, điều duy nhất trình biên dịch phải theo dõi là liệu đó là biến toàn cục hay biến cục bộ và địa chỉ của nó so với toàn cục hoặc cục bộ đầu tiên Biến đổi.

Đưa ra một khai báo toàn cục như i;[không cần chỉ định một loại, vì mọi thứ đều là số nguyên / con trỏ] sẽ được trình biên dịch xử lý như sau: address_of_i = next_global++; memory[address_of_i] = 0;và một câu lệnh như i++sẽ được xử lý như sau:memory[address_of_i] = memory[address_of_i]+1; .

Một tuyên bố như arr[10];sẽ được xử lý như address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;. Lưu ý rằng ngay khi khai báo đó được xử lý, trình biên dịch có thể ngay lập tức quên đi việc arrlà một mảng . Một tuyên bố như arr[i]=6;sẽ được xử lý như memory[memory[address_of_a] + memory[address_of_i]] = 6;. Trình biên dịch sẽ không quan tâm đến việc arrđại diện cho một mảng và imột số nguyên hay ngược lại. Thật vậy, sẽ không quan tâm nếu cả hai đều là mảng hoặc cả hai số nguyên; nó hoàn toàn vui vẻ tạo mã như được mô tả, mà không cần quan tâm đến việc liệu hành vi kết quả có thể hữu ích hay không.

Một trong những mục tiêu của ngôn ngữ lập trình C là tương thích phần lớn với B. Trong B, tên của một mảng [được gọi là "vectơ" theo thuật ngữ của B] đã xác định một biến giữ một con trỏ ban đầu được gán cho đến phần tử đầu tiên của phân bổ kích thước đã cho, vì vậy nếu tên đó xuất hiện trong danh sách đối số cho hàm, hàm sẽ nhận được một con trỏ tới vectơ. Mặc dù C đã thêm các kiểu mảng "thực", nhưng tên của nó được liên kết chặt chẽ với địa chỉ của phân bổ chứ không phải là một biến con trỏ ban đầu chỉ đến phân bổ, có các mảng phân tách thành các con trỏ tạo mã mà khai báo một mảng loại C hoạt động giống hệt nhau thành mã B đã khai báo một vectơ và sau đó không bao giờ sửa đổi biến giữ địa chỉ của nó.


1

Trên thực tế &myarraymyarraycả hai đều là địa chỉ cơ sở.

Nếu bạn muốn thấy sự khác biệt thay vì sử dụng

printf("my_array = %p\n", my_array);
printf("my_array = %p\n", &my_array);

sử dụng

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
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.