在C语言中,array、&array和&array[0]有什么区别?

5

在学习C语言中的数组和指针时,我感到困惑,为什么ch、&ch、&ch[0]相等,而sptr、&sptr、&sptr[0]不相等呢?以下是我的源代码:

int main(void)
    {
        char ch[7] = { '1','2','3','4','5','6','0' };
        char *sptr = "132456";
        double db[4] = { 1.0,2.0,3.0,4.0 };
        printf("\n%p  %p  %p\n", ch, &ch,&ch[0]);
        printf("\n%p  %p  %p\n", sptr, &sptr,&sptr[0]);
        printf("\n%p  %p  %p\n", db, &db,&db[0]);
        return 0;

    }

我的机器上的输入为:

00FDFD68  00FDFD68  00FDFD68

00037CD0  00FDFD5C  00037CD0

00FDFD34  00FDFD34  00FDFD34

1
请查看此链接:https://dev59.com/QZ3ha4cB1Zd3GeqPXqm5#41329292 - Sourav Ghosh
所有的 printf 调用都会因为传递错误类型给 %p 转换类型说明符而引发未定义行为。 - too honest for this site
@Olaf 是正确的;为了准确,你应该将每个值转换为 (void *)。在大多数真实系统上,这不会有任何区别,但是最好在可能的情况下保持正确,因为已知某些类似的“无害”错误会导致问题。 - davmac
2个回答

10

在大多数(但不是全部)情况下,数组会“衰变”为指向其第一个元素的指针。这就是为什么在您的示例中 ch&ch[0] 是相同的原因(数组元素访问具有比“取地址”运算符更高的优先级,因此后者也可以写成 &(ch[0]))。

剩下的&ch是数组不会衰变为指针的一种情况;相反,您会得到该数组的地址。自然而然的,这与数组的第一个元素的地址相同 - 但是,重要的是,它具有不同的类型;它是类型为char (*)[7]的指向包含7个元素的字符数组的指针。另外两个指针的类型是char *,即指向单个char的指针。

由于sptr是一个指针,&sptr是该指针的地址,自然会不同。 &sptr[0]等同于sptr + 0,这当然等于sptr

你不理解为什么sptr&sptr产生不同的地址,这表明了对指针的理解有误。指针是一个固定大小的对象,其值可以指向(引用)某个特定类型的任意对象。由于它本身是一个对象,因此可以将指针指向不同的对象。另一方面,数组变量始终(在其生命周期内)引用同一个数组对象。

在您的输出示例中:

00037CD0  00FDFD5C  00037CD0

第一个值00037CD0是sptr指向的位置——也就是说,它是内存中字符串常量“132456”的位置。第二个值00FDFD5C是sptr变量本身的地址。这表明,在地址00FDFD5C处有一个指针对象,它保存着值00037CD0。

基本上,两种情况之间的差异归结为以下两点:

  • 数组的地址与其第一个元素的地址相同
  • 另一方面,指针的地址与指针当前指向的内容无关。

@Olaf,我们之前已经讨论过这个问题。我认为没有必要再重复一遍,但我会总结一下:数组的第一个元素在存储中的地址与数组本身在存储中的地址_必然_相同。 "地址"运算符返回一个指针,包括一个地址值和一个类型;两种情况下的类型将不同,在答案中已经很清楚了。 - davmac
嗯,这一次我必须道歉。你的措辞很不幸。“appear”在这里太弱了。它们不仅看起来相同,而且在这里产生了完全相同的结果(值和类型,没有区别)。这让我感到困惑。为了让我理解正确:这次是我的错,但请重新表述以强调它们在这里的所有方面都是等效的。 - too honest for this site
1
@Olaf,好的,我已经将“appear”更改为“are”。请撤回您的反对票。 - davmac
完成。但请查看我对问题的评论。 - too honest for this site
数组的地址与其第一个元素的地址相同,这两个地址将等同于彼此,它们不必具有相同的二进制表示。由于是不同类型,两个指针类型的大小甚至可能不同。当然,具有相同的二进制表示和大小是压倒性的常见实现细节。但是旧的、奇怪的,甚至是新的新颖架构可能会有所不同。 - chux - Reinstate Monica
显示剩余3条评论

5
如果我们在内存中“画”出来,你的数组ch会像这样:
+-----+-----+-----+-----+-----+-----+-----+
| '1' | '2' | '3' | '4' | '5' | '6' | '0' |
+-----+-----+-----+-----+-----+-----+-----+
^     ^
|     |
|     &ch[1]
|
&ch
^
|
&ch[0]
如您所见,&ch&ch[0]都指向同一位置。但是两个表达式之间有一个重要的区别:&ch是指向数组的指针,而&ch[0]是指向数组中的元素的指针。这两个指针具有不同的类型:&ch的类型为char (*)[7],而&ch[0]的类型为char *。即使两者都是指向相同位置的指针,类型上的差异使它们在语义上非常不同。
现在让我们看看array如何适应这一切。对于任何数组或指针a和索引i,表达式a[i]等于*(a + i)。这意味着&a[i]等于&*(a + i)。取地址和解引用运算符相互抵消,这意味着&a[i]等于(a + i)。如果索引为零,则有&a[0]等于(a + 0),但由于将零添加到任何内容都是无操作,因此它也等于(a),即等于a。因此,&a[0]等于a。当您看到有人说一个数组“衰变”为指向其第一个元素的指针时,这就是他们的意思。在表达式中使用数组a与使用&a[0]相同。
另外需要注意的是,数组ch可能是字符数组,但它不是字符串。这是因为在C语言中,字符串真正被称为空终止字符串。这里的“null”不是空指针,而是整数(不是字符)零。
这意味着您不能将ch用于任何需要字符串的函数,否则会导致未定义行为,因为这些函数会越界查找终止符。

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