为什么在C语言中,数组的地址等于其值?

234
在以下的代码中,指针值和指针地址按预期不同。
但是数组的值和地址却没有!
为什么会这样呢?
输出结果:
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;
}

3
我曾在两年前为这个问题添加了一个带有图表的答案,请参考此处:What does sizeof(&array) return? - Grijesh Chauhan
这回答了你的问题吗?什么是数组指针衰减? - Andreas Wenzel
6个回答

252
数组的名称通常被解释为数组的第一个元素的地址,因此array&array具有相同的值(但是不同的类型,所以如果数组超过1个元素,则array+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个字符组成的数组)。由于%p通常将指针转换为十六进制,因此它可能看起来像这样:
0x12341000    0x12341010

3
@Alexandre:&array是指向数组第一个元素的指针,而array则引用整个数组。通过比较sizeof(array)sizeof(&array)也可以观察到这种基本差异。但请注意,如果将array作为参数传递给函数,则实际上只会传递&array。除非将其封装在struct中,否则无法按值传递数组。 - Clifford
19
如果您将数组传递给函数,它会被转换为指向其第一个元素的指针,因此实际上传递的是&array[0],而不是指向整个数组的&array指针。这可能是一个小问题,但我认为明确说明很重要;如果函数具有与所传递指针类型匹配的原型,则编译器会发出警告。 - CB Bailey
2
例如 int *p = &a,如果我想要整型指针 p 的内存地址,我可以使用 &p。由于 &array 转换为整个数组的地址(从第一个元素的地址开始)。那么我如何找到数组指针的内存地址(它存储数组第一个元素的地址)?它一定在内存中某个地方吧? - John Lee
2
@JohnLee:不需要在内存中有指向数组的指针。如果你创建一个指针,你可以取它的地址:int *p = array; int **pp = &p; - Jerry Coffin
3
@Clifford的第一条评论是错误的,为什么还要保留它?我认为这可能会导致那些没有阅读下面(@Charles)回复的人产生误解。 - Rick
显示剩余9条评论

38
那是因为“数组名称”(`my_array`)与指向数组的指针不同。它是数组地址的别名,并且其地址被定义为数组本身的地址。
然而,指针是一个普通的C变量,位于堆栈上。因此,您可以获取其地址并从其内部持有的地址获取不同的值。
我在这个主题上写了一篇文章,请看这里

@Alexandre:实际上我不确定为什么它被允许。 - Eli Bendersky
您可以获取任何变量的地址(如果没有标记为“寄存器”),无论其存储期限是静态的、动态的还是自动的。 - CB Bailey
my_array 本身就在堆栈上,因为 my_array 就是整个数组。 - caf
3
my_array 不是 &sizeof 运算符的主语时,它会被评估为指向其第一个元素的指针(即 &my_array[0])- 但是,my_array 本身不是该指针(my_array 仍然是数组)。该指针只是一个短暂的右值(例如,给定 int a;,它就像 a + 1)-至少在概念上是“根据需要计算的”。my_array 的真正“值”是整个数组的内容 - 只是在 C 中确定此值就像试图将雾气装进罐子里一样难。 - caf
感谢您详细的博客文章。不过您漏掉了一件事:我可以想象出指向数组的完美使用方式。它是为了在类型系统中编码数组的大小。我认为这主要是为了人类读者,但还有更多:如果您尝试传递指向不同大小的数组的指针,编译器会警告您,例如“expected ‘int ()[5]’ but argument is of type ‘int ()[4]’”。 - Hi-Angel
显示剩余3条评论

31

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

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

在您的示例中,my_array的类型为char [100],当您将其传递给printf函数时,它会衰减为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都直接指向数组或数组的第一个元素,因此具有相同的地址值。


6
< p > 当你查看数组的内存布局时,就可以很容易地理解为什么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].

5
在 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 是一个数组。类似 arr[i]=6; 的语句将被处理为 memory[memory[address_of_a] + memory[address_of_i]] = 6;。编译器不关心 arr 是否表示数组、i 是否表示整数以及它们是否都是数组或整数。它会按照描述生成代码,而不考虑结果行为是否有用。
C 语言的一个目标是与 B 语言兼容。在 B 中,数组名称(在 B 的术语中称为“向量”)标识一个变量,该变量持有一个初始分配给给定大小第一个元素的指针。因此,如果将该名称出现在函数的参数列表中,函数将接收指向该向量的指针。尽管 C 添加了“真正”的数组类型,其名称与分配的地址紧密关联,而不是一个最初指向该分配的指针变量,但将数组分解为指针使得声明 C 类型数组的代码表现出与声明向量然后从未修改保存其地址的变量的 B 代码相同。

虽然晚了近十年,但这绝对是迄今为止最有见地的回答。谢谢! - undefined

2

实际上,&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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接