数组的地址如何等于C中的值?


189

在下面的代码位中,指针值和指针地址与预期的不同。

但是数组值和地址不行!

怎么会这样?

输出量

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;
}

来自comp.lang.c常见问题解答:-[那么C中的“指针和数组的等效性”是什么意思?](c-faq.com/aryptr/aryptrequiv.html)-[由于数组引用会衰减为指针,如果arr是数组,则arr和&arr有什么区别?](c-faq.com/aryptr/aryvsadr.html)或阅读整个“ 数组和指针”部分。
jamesdlin

3
两年前,我已经在此问题上添加了带有图表的答案,返回了什么sizeof(&array)
Grijesh Chauhan

Answers:


212

数组的名称通常以数组的第一个元素的地址求值,因此,array&array具有相同的值(但类型不同,因此array+1,如果数组长度超过1个元素,&array+1则将相等)。

对此有两个例外:当数组名称是sizeof或一元操作数&(地址)时,该名称引用数组对象本身。因此sizeof array,您可以得到整个数组的大小(以字节为单位),而不是指针的大小。

对于定义为的数组T array[size],它将具有类型T *。当/如果增加它,您将到达数组中的下一个元素。

&array计算结果到相同的地址,但是给定相同的定义,它将创建该类型的指针T(*)[size]-即,它是指向数组而不是单个元素的指针。如果增加此指针,它将增加整个数组的大小,而不是单个元素的大小。例如,使用如下代码:

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

我们可以预期第二个指针比第一个指针大16(因为它是16个char的数组)。由于%p通常将指针转换为十六进制,因此它可能类似于:

0x12341000    0x12341010

3
@Alexandre:&array是指向数组第一个元素的指针,其中as array表示整个数组。通过与进行比较sizeof(array),也可以观察到根本差异sizeof(&array)。但是请注意,如果您将array参数作为参数传递给函数,&array则实际上只会传递。您不能按值传递数组,除非将其封装为struct
克利福德

14
@Clifford:如果将数组传递给函数,则它会衰减为指向其第一个元素的指针,从而有效地&array[0]被传递,而不是&array指向数组的指针。可能是挑剔,但我认为必须弄清楚。编译器会发出警告,如果函数有传入的指针的类型相匹配的原型。
CB贝利

2
@Jerry Coffin例如int * p =&a,如果我想要int指针p的内存地址,则可以执行&p。由于&array转换为整个数组的地址(从第一个元素的地址开始)。然后,如何找到数组指针的内存地址(该指针存储数组中第一个元素的地址)?它一定在内存中的某个地方吧?
约翰·李

2
@JohnLee:不,在内存中的任何地方都不必指向数组。如果创建指针,则可以使用其地址:int *p = array; int **pp = &p;
杰里·科芬

3
@Clifford第一个评论是错误的,为什么还要保留呢?我认为对于不阅读以下(@Charles)答复的人可能会引起误解。
里克

30

这是因为数组名称my_array)与指向数组的指针不同。它是数组地址的别名,其地址定义为数组本身的地址。

但是,指针是堆栈上的普通C变量。因此,您可以获取其地址,并获得与其内部保存的地址不同的值。

在这里写过关于这个话题的文章-请看一看。


&my_array应该不是无效的操作,因为my_array的值不在堆栈中,只有my_array [0 ... length]在堆栈中吗?然后一切都会变得有意义...
亚历山大

@亚历山大:我不确定为什么允许这样做。
Eli Bendersky 2010年

无论变量register的存储时间是多少,您都可以使用其地址(如果未标记):静态,动态或自动。
CB Bailey 2010年

my_array本身在堆栈上,因为my_array 整个数组。
caf 2010年

3
my_array如果不是&or sizeof运算符的主题,则被评估为指向其第一个元素(即&my_array[0])的指针-但my_array其本身不是该指针(my_array仍是数组)。该指针只是一个短暂的右值(例如,给定int a;,就像a + 1)-至少从概念上讲,它是“根据需要计算的”。真正的“值” my_array是整个数组的内容-只是将这个值固定在C中就像在罐子中捕捉雾一样。
caf 2010年

27

在C语言中,当您在表达式中使用数组名称(包括将其传递给函数)时,除非它是address-of(&)操作符或该sizeof操作符的操作数,否则它将衰减为指向其第一个元素的指针。

也就是说,在大多数情况下,类型和值都array相等&array[0]

在您的示例中,当您将其传递给printf时,my_array其类型char[100]会衰减为a char*

&my_array具有类型char (*)[100](指向100的数组的指针char)。由于它是的操作数&,因此是其中一种情况my_array不会立即衰减到指向其第一个元素的指针。

指向数组的指针与指向数组的第一个元素的指针具有相同的地址值,因为数组对象只是其元素的连续序列,但是指向数组的指针与指向元素的指针的类型不同该数组。当您对两种类型的指针进行指针运算时,这一点很重要。

pointer_to_array具有类型char *-初始化为指向数组的第一个元素,因为它是my_array初始化程序表达式中会衰减的元素- &pointer_to_array 具有类型char **(具有指向的指针char)。

这些:my_array(衰变到后char*&my_arraypointer_to_array在任一阵列或阵列等的第一元件直接所有点具有相同地址值。


3

当您查看阵列的内存布局时,可以很容易地理解my_array&my_array导致相同地址的原因。

假设您有10个字符的数组(而不是代码中的100个字符)。

char my_array[10];

内存my_array看起来像:

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

在C / C ++中,数组会衰减到表达式中第一个元素的指针,例如

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

如果检查数组的第一个元素在哪里,您将看到其地址与数组的地址相同:

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

3

在B编程语言(它是C的直接前身)中,指针和整数可以自由互换。系统的行为就好像所有内存都是一个巨大的阵列。每个变量名都具有与之关联的全局或相对于堆栈的地址,对于每个变量名,编译器唯一需要跟踪的是它是全局变量还是局部变量,以及其相对于第一个全局变量或局部变量的地址变量。

给定一个全局声明,例如i;[不需要指定类型,因为所有内容都是整数/指针]将由编译器处理为:address_of_i = next_global++; memory[address_of_i] = 0;和类似语句的i++处理为:memory[address_of_i] = memory[address_of_i]+1;

arr[10];这样的声明将被处理为address_of_arr = next_global; memory[next_global] = next_global; next_global += 10;。请注意,一旦处理arr该声明,编译器可能会立即忘记成为array。类似的语句arr[i]=6;将被处理为memory[memory[address_of_a] + memory[address_of_i]] = 6;。编译器将不在乎是否arr表示一个数组和i一个整数,反之亦然。确实,它们都是数组还是整数都不在乎。它会完全愉快地生成所描述的代码,而不考虑所产生的行为是否可能有用。

C编程语言的目标之一是在很大程度上与B兼容。在B中,数组的名称(在B的术语中称为“向量”)标识了一个变量,该变量持有一个指针,该指针最初被分配为指向到给定大小分配的第一个元素,因此,如果该名称出现在函数的参数列表中,则该函数将收到指向该向量的指针。即使C添加了“真实的”数组类型,其名称与分配的地址严格相关,而不是最初指向该分配的指针变量,但将数组分解为指针后,使声明C类型数组的代码行为相同到声明了向量的B代码,然后再不修改保存其地址的变量。


1

实际上&myarraymyarray两者都是基址。

如果您想看到差异而不是使用

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

printf("my_array = %s\n", my_array);
printf("my_array = %p\n", my_array);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.