【问题标题】:What is (&array)[1] exactly?(&array)[1] 到底是什么?
【发布时间】:2021-07-26 21:12:36
【问题描述】:

这个 Q in C, how come the address of array, &array, &array[0] are the same? 让我研究了指针和数组:我确认它们非常相似但不完全相同。

&a[0]a&a 的详细形式。但是& vs [] 的优先级呢?

当我像(&a)[1] 这样错误地放置括号时,这会改变值,但不会改变(&a)[0]

#include <stdio.h>


int main(){
        int a[3] = {10,20,30};

        int *b;
        b = a;

        printf("%p a\n", a);
        printf("%p a+1\n", a+1);
        printf("%p a+2\n\n", a+2);

        printf("%p (&a)[0]\n",  (&a)[0]);
        printf("%p (&a)[1]\n",  (&a)[1]);
        printf("%p (&b)[0]\n",  (&b)[0]);
        printf("%p (&b)[1]\n\n",(&b)[1]);
        printf("%p cast\n\n", (void **)((&a) + 1)   );
        int *c = a;
        printf("%p\n",  c);
        printf("%p\n",  &c);
return 0;
}

输出:

0x7ffe18053c9c a
0x7ffe18053ca0 a+1
0x7ffe18053ca4 a+2

0x7ffe18053c9c (&a)[0]
0x7ffe18053ca8 (&a)[1]
0x7ffe18053c9c (&b)[0]
(nil)          (&b)[1]

0x7ffe18053ca8 cast

0x7ffe18053c9c c  (int *c = a)
0x7ffe18053c90 &c       

我发现(void **)((&amp;a) + 1) 得到了相同的结果。似乎不能漏掉一块。突然&amp;a确实匿名创建了一个指针。

使用索引 0,您可以通过 a 及其指针 b 获得基址 (...c9c)。但是对于索引 1,会发生一些奇怪的事情,而 b 则不同。

这有规则吗?我什至不知道我想不想听!除了介于a&amp;a[0] 之间之外,这就是&amp;a 的作用吗?


chux 的好 A。他的演员阵容更简单,但也一样吗?

difference between &array and &array[0] in C


从提到的链接中,我不仅简化了我的演员阵容,而且现在我想我知道它的作用以及为什么 0 的索引或偏移量是特殊的。

(void *)(&amp;a + 0) 是演员。但为什么还要投呢? &amp;a + 0 打印基地址。使用偏移量+ 1,地址会增加整个数组的大小(而不是我尝试过的指针)——“一个过去的结尾”。第二个维度。基数保持不变,但偏移量发生了变化。

分配:

int *c = a;
int (*d)[3] = &a;    // with some help from the left...
int *e = (int *)&a;  // ... or on the right side...
int *f = &a;         // or with a warning, but working

所以(&amp;a) 生成一个新类型,但不是指针?对同一地点的新参考。 = 指向 = 数组的指针的地址。

【问题讨论】:

  • 通过C operator precedence,您可以很快确定它将做什么:首先我们使用下标,然后使用地址运算符(不存在括号)。上面代码的窍门是数组的地址和第一个元素的地址相同(a[0]),所以&amp;a[0] == &amp;(*a)。另一个技巧是数组“衰减”成指针。 a 是一个数组类型,但&amp;a(或&amp;any_variable)将是变量的地址,它本质上是一个指针。

标签: arrays c syntax


【解决方案1】:

TL;DR:

  • &amp;a[0] 返回数组a 的第一个元素的地址。由于 C 中的数组几乎一直自动衰减(转换)为指向它的第一个元素的指针,因此您将通过查看 a&amp;a[0] 观察到相同的行为。但请注意,有些情况下不会发生这种转换,最明显的一种是在数组上使用 sizeof 运算符。

  • (&amp;a)[0] 获取数组的地址,然后将结果视为数组并尝试访问它的第一个元素。这与仅取消引用结果指针相同,因此(&amp;a)[0]*(&amp;a) 相同,与a 相同。比见上文。

  • (&amp;a)[1] 几乎和前面一样,但它尝试访问由指向数组a 的指针构造的假设数组的 second 元素。由于这实际上不是一个数组,并且没有第二个元素,因此这段代码格式错误,并触发 Undefined Behavior

【讨论】:

  • "...将 [数组地址] 视为一个数组...自身的数组",可能会添加。您非常直接地回答了我的 Q。 仍然是带有“b”的对称指针版本。但这归结为一个指针数组,只要“b”可以采取偏移量。
【解决方案2】:

&a[0] 是 a 或 &a 的详细形式。

&amp;a 产生相同的地址值,但类型不同。这很重要。

但是 & vs [] 的优先级呢?

&amp;* 这样的一元运算符的优先级低于像[]() 这样的后缀运算符,所以&amp;a[0] 被解析为&amp;(a[0]) - 你正在使用a[0] 的地址。

当您编写(&amp;a)[0] 时,您正在索引&amp;a 的结果。表达式&amp;a 的类型为“指向int 的三元素数组的指针”。下标操作a[i] 等价于表达式*(a + i) - 给定起始地址a,从该地址偏移i 元素(不是字节!)并取消引用结果 1.

所以表达式(&amp;a)[0]等价于*(&amp;a + 0),等价于*&amp;a,等价于a,其类型为“int的三元素数组”。除非这个表达式是sizeof 或一元&amp; 运算符的操作数(如&amp;(&amp;a)[0]),否则它将“衰减”为“指向int”类型的表达式,并且表达式的值将是数组的第一个元素的地址。

表达式(&amp;a)[1] 等价于*(&amp;a + 1)。和上面一样,这个表达式的类型是“int 的三元素数组”,除非它是 sizeof 或一元 &amp; 运算符的操作数,否则它“衰减”为类型为“pointer to”的表达式int" 并且表达式的值将是数组第一个元素的地址。

不过……

由于&amp;a 的类型为“指向int 的三元素数组的指针”,&amp;a + 1 产生int 之后的下一个三元素数组的地址 a

+----  +----+
|      | 10 | a[0]  <--- (&a)[0]
|      +----+
a      | 20 | a[1]
|      +----+
|      | 30 | a[2]
+----  +----+
|      | ?? |       <--- (&a)[1] 
|      +----+
??     | ?? |
|      +----+
|      | ?? |
+----  +----+

  1. 指针算法考虑了指向类型的大小。如果p 指向T 类型的对象,那么p + 1 指向它后面的T 类型的next 对象,而不是下一个字节。数组下标是根据指针算术定义的——这就是数组表达式首先“衰减”到指针的原因。

【讨论】:

  • (&a)[0][1] 实际上与 a[1] (20) 相同。就像说“想象一座高楼……一楼,其他的还没有建成”。这首先应该给我一个编译错误!但是感谢您剖析这个毫无用处的结构。
  • @cotentin:你认为它为什么会给你一个编译错误?同样,(&amp;a)[0] 解析为a,所以(&amp;a)[0][1] == a[1]。这很丑陋,不直观,但它是有效的。
  • 我的意思是我的整个 (&array)[1] 想法。我希望这个丑陋、不直观和无用的结构也无效。太让我困惑了。当我尝试时,我确信这将毫无意义。然后它似乎意味着某事。 (我也做了一个指针版本......),现在我明白它意味着什么。一定的逻辑满足感,承认,但零用处。
【解决方案3】:

首先,如果您是初学者,我建议您暂时不要担心这些事情,因为它们会让人感到困惑并且实际用途有限。您已被警告:) 下面是一些高级答案。


&a[0] 是 a 或 &a 的详细形式

不是。

  • &amp;a[0] 是数组第一个元素的地址。
  • a 是数组,在大多数情况下,只要在表达式中使用,它就会“衰减”为指向第一个元素的指针。因此,它可能与 &amp;a[0] 相同也可能不同,具体取决于上下文。
  • &amp;a 是“数组本身”的地址。这与第一个元素的地址相同,但类型不同。这个&amp; 是数组不“衰减”的少数表达式之一。你在这里得到一个指向数组的指针。给定int a[n] 然后&amp;a 给你类型int (*)[n]。这与指向第一个元素的指针不兼容,它们是不同的类型。

现在(&amp;a)[0] 改变了事物的含义。对于&amp;a[0][] 具有最高优先级,因此我们在应用&amp; 之前取消引用数组元素。但是有了括号,我们首先得到一个指向数组的指针,然后我们取消引用那个。这很有效,因为 C 也允许对这些指针类型进行指针运算。结果就像我们有一个数组数组一样完成了指针运算。我们没有,所以你会得到非常奇怪的结果,每个[] 在内存中跳转一个int[3] 数组而不是一个int

可以像这样使用指向数组的指针:

int a[2][3] = { {10,20,30} };
int (*p)[2][3] = &a;
printf("%d %d %d", (*p)[0][0], (*p)[0][1], (*p)[0][2]);

虽然这很难阅读,却一无所获。一个技巧是删除最左边的维度,从而跳过一级取消引用:

int a[2][3] = { {10,20,30} };
int (*p)[3] = a;
printf("%d %d %d", p[0][0], p[0][1], p[0][2]);

【讨论】:

  • 很好的修正。 “其中一个少数案例”“我们没有”:我现在真的明白了,很好的解释,谢谢!
  • @cotentin 除了我的语法显然很糟糕,会修复:)
  • 不!你的风格/语法在这种情况下是完美的!正如我在 Q 中所说:“我什至不知道我是否想听听所有的特殊规则”。但我喜欢你的故事。现在还是很好,修复后:)
猜你喜欢
  • 1970-01-01
  • 2011-12-28
  • 2022-11-16
  • 2015-09-20
  • 1970-01-01
  • 1970-01-01
  • 2011-12-01
  • 2022-06-16
  • 2011-08-15
相关资源
最近更新 更多