指向数组第一个元素的指针的地址是什么?

3
我对指向数组开头的指针在内存中的位置感到有些困惑。据我理解,数组和指针可以使用相同的函数,如*、&和[]。所以如果我创建一个char数组,我理解:buffer == &buffer[0]。
因此,没有方括号的数组名就像一个指针,它包含了数组第一个条目的地址,对吗?
但是当我试图找出指针的地址(&buffer[0]存储的地址),它将给我与指针(&arrayname[0])中存储的相同值。这怎么可能?同一个虚拟RAM中的地址既包含一个地址,又包含buffer[0]的值(在下面的代码中等于'H')?
#include <stdio.h>
#include <windows.h>

void main() {

  char buffer[] = "Hello";

  printf("Address buffer: %d\n", &buffer);
  printf("Value buffer: %d\n", buffer);
  printf("Address buffer[0]: %d\n", &buffer[0]);
  printf("Value buffer[0]: %c\n", buffer[0]);
  printf("Address buffer[1]: %d\n", &buffer[1]);

  system("pause");
}

基本上我对第一个输出感到困惑。它难道不应该与第二个输出不同吗?非常感谢您的解释... 问候

4
请阅读一本优秀的C(或C++)编程书籍,它们会解释所有细节(基本上,数组可以衰减为指针)。并使用“%p”来打印指针。 - Basile Starynkevitch
@BasileStarynkevitch,他的问题不是这个。 - SwiftMango
要打印地址,请使用 %p 转换说明符。 - alk
1
已经在这里回答了:https://dev59.com/4nE85IYBdhLWcg3w9odJ - Kissiel
好的,谢谢大家。简而言之,& 运算符在处理数组本身时不能像预期那样工作。但是,如果我对第一个条目的地址所存储的地址感兴趣,我该怎么办呢?有没有办法找出来? - user2352375
2个回答

9

除非它是sizeof或一元&运算符的操作数,或者是用于声明中初始化另一个数组的字符串字面量,否则类型为“N元素数组 of T”的表达式将被转换(“衰减”)为类型为“指向T的指针”的表达式,并且该表达式的值将是数组第一个元素的地址。

假设以下代码:

char buffer[] = "Hello";
...
printf( "%s\n", buffer );

在调用printf函数时,表达式buffer的类型是“6元素数组”,由于它不是sizeof或一元&运算符的操作数,也没有在声明中用于初始化另一个数组,因此该表达式被转换(“衰减”)为类型为“指向的指针”(char *)的表达式,而表达式的值是数组中第一个元素的地址。
现在,请将printf调用更改为:
printf( "%p", (void *) &buffer );

这次,buffer是一元运算符&的操作数;不会自动转换为类型“指向char的指针”。相反,表达式&buffer的类型是“指向6元素数组的char的指针”,或char (*)[6] 1(括号很重要)。
两个表达式产生相同的值--数组的地址与数组第一个元素的地址相同--但是两个表达式的类型是不同的。这很重要;char *char (*)[6]不能互换。
那么,为什么在第一次设计C语言时会存在这种奇怪的转换魔法呢?
当Dennis Ritchie最初设计C语言时,他是在基于一种名为B的早期语言进行设计(可以想象)。当你在B中分配一个数组时,就像这样:
auto arr[N];

编译器会为数组内容预留N个元素,以及一个额外的单元格来存储数组第一个元素的偏移量(基本上是一个指针值,但没有任何类型语义;B是一种“无类型”语言)。这个额外的单元格将绑定到变量arr,你会得到以下内容:
          +---+
arr:      |   | --+
          +---+   |
           ...    |
          +---+   |
arr[0]:   |   | <-+
          +---+
arr[1]:   |   |
          +---+
arr[2]:   |   |
          +---+
 ...       ...
          +---+
arr[N-1]: |   |
          +---+

起初,Ritchie保留了这些语义,但是当他开始向C语言添加结构体类型时遇到了问题。他希望结构体类型能够直接编码它们的字节;换句话说,考虑到一个像以下这样的类型:

struct {
  int inode;
  char name[14];
};

他需要一个2字节的整数,紧跟着是一个14字节的数组;没有一个好地方藏指向数组第一个元素的指针。
于是他将其摒弃了;不再为指向数组第一个元素的指针设置存储空间,而是设计语言使得数组的位置从数组表达式本身计算出来。因此,这篇文章开始就有了这条规则。
1. %p转换指示符希望其对应参数是一个void *表达式,因此需要强制类型转换。

哇,你的回答真的让我印象深刻,解释得非常清楚,非常感谢你抽出时间来回答。关于整个“从数组表达式计算”的问题,你能否给我提供一些相关文献的链接或名称? - user2352375
@user2352375:嗯,你可以从最新标准(在线草案)开始;6.3.2.1节是最相关的。Ritchie还撰写了这篇论文,描述了他如何开发C语言。 - John Bode

-2

这是错误的:

printf("Address buffer: %d\n", &buffer);
printf("Value buffer: %d\n", buffer);

假设数组是指针的正确代码应该是:
printf("Address buffer: %p\n", (void*) buffer); 
printf("Value buffer: %d\n", *buffer);

关于编程的内容,你可以把 array[x] 翻译成 *(array + x),它的意思如下:

读取指向数组 array + x 的内存区域,其大小为元素大小


1
不,你应该使用 %pprintf 指针(然后最好将它们显式转换为 void*)。 - Basile Starynkevitch
抱歉,也许我的输出有点误导人:“值缓冲区:”我指的是值缓冲区本身相等,因此在这种情况下是一个地址,而不是地址所指向的值。 - user2352375

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