获取数组变量的地址意味着什么?

wha*_*old 26 c pointers

今天我读了一个让我感到困惑的C片段:

#include <stdio.h>

int
main(void)
{
    int a[] = {0, 1, 2, 3};

    printf("%d\n", *(*(&a + 1) - 1));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,&a + 1毫无意义,但它运行没有错误.

有人可以解释一下这意味着什么,谢谢.K&R C圣经是否涵盖了这一点?

UPDATE0:读完答案后,我意识到这两个表达式主要让我困惑:

  1. &a + 1,在SO中被问到:关于c中的表达"&anArray"

  2. *(&a + 1) -1,这与阵列衰减有关.

Som*_*ude 36

首先是一个小提醒(如果之前你不知道这个,那就是新的东西):对于任何数组或指针p和索引i,表达式p[i]都与之完全相同*(p + i).

现在希望能帮助您了解正在发生的事情......

a程序中的数组存储在内存中的某个位置,这实际上并不重要.为了得到这里的位置a存储,即获得一个指针a,你用地址的操作&类似&a.这里要学习的重要一点是,指针本身并不意味着什么特别,重要的是指针的基本类型.ais 的类型int[4],即a四个int元素的数组.表达式的类型&a是指向四个数组的指针int,或int (*)[4].括号很重要,因为类型int *[4]是四个指针的数组int,这是完全不同的事情.

现在回到初始点,p[i]就是这样*(p + i).而不是p我们&a,所以我们的表达*(&a + 1)是相同的(&a)[1].

现在,这解释*(&a + 1)了它的意义和作用.现在让我们考虑一下数组的内存布局a.在内存中它看起来像

+---+---+---+---+
| 0 | 1 | 2 | 3 |
+---+---+---+---+
^
|
&a

该表达式(&a)[1]视为&a一个数组数组,它​​肯定不是,并访问此数组中的第二个元素,这将超出范围.这当然在技术上是未定义的行为.让我们暂时运行它,并考虑在内存中的样子:

+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | . | . | . | . |
+---+---+---+---+---+---+---+---+
^               ^
|               |
(&a)[0]         (&a)[1]

现在请记住,类型a((&a)[0](&a)[1]此类型相同,因此也必须是此类型)是四个数组int.由于数组自然地衰减到指向其第一个元素的指针,因此表达式与其(&a)[1]相同&(&a)[1][0],并且其类型是指针int.因此,当我们(&a)[1]在表达式中使用时,编译器给出的是指向第二个(不存在的)数组中第一个元素的指针&a.我们再一次得到p[i]等于*(p + i)等式:(&a)[1]是一个指针int,它p*(p + i)表达式中,所以完整的表达式是*((&a)[1] - 1),并且查看上面的内存布局int从给定的指针中减去一个(&a)[1]给出了前面的元素(&a)[1]是最后一个元素in (&a)[0],即它给了我们与之(&a)[0][3]相同的a[3].

所以表达式*(*(&a + 1) - 1)是一样的a[3].

它是啰嗦,并且经过危险的领域(越界索引),但由于指针算术的力量,它最终都有效.我不建议您编写这样的代码,但是需要人们真正了解这些转换如何能够解密它.

  • +无穷大"这当然在技术上是_undefined behaviour_." 超出范围的访问不应该被用作"聪明"的黑客.不是.这似乎在概念上有意义,但不需要一丝不苟地工作.很难相信你是唯一一个提到这个的人! (4认同)
  • @supercat没有必要抓住40岁的偶然事件 (3认同)

Ian*_*ott 13

让我们剖析它.

a有类型int [4](数组为4 int).它的大小是4 * sizeof(int).

&a有类型int (*)[4](指向4 int数组的指针).

(&a + 1)也有类型int (*)[4].它指向一个4 int的数组,它在开始之后开始1 * sizeof(a)字节(或4 * sizeof(int)字节)a.

*(&a + 1)是类型int [4](4 int的数组).它的存储开始是1 * sizeof(a)字节(或4 * sizeof(int)开始后的字节)a.

*(&a + 1) - 1是类型int *(指向int的指针),因为数组*(&a + 1)衰减到指向此表达式中第一个元素的指针.它将指向一个在开始1 * sizeof(int)之前开始字节的int *(&a + 1).这与指针值相同&a[3].

*(*(&a + 1) - 1)是类型的int.因为*(&a + 1) - 1是相同的指针值&a[3],*(*(&a + 1) - 1)相当于a[3]已经初始化为3,所以这是由打印的数字printf.


Ser*_*gio 7

&a + 1将指向紧跟在最后一个a元素之后的内存或更好地说在a数组之后,因为它&a具有类型int (*)[4](指向四个数组的指针int).标准允许构造此类指针,但不允许解除引用.因此,您可以将其用于后续的算术.

所以,结果*(&a + 1)是未定义的.但是*(*(&a + 1) - 1)更有趣的是.实际上它被评估为最后一个元素.a有关详细说明,请参阅/sf/answers/2674172861/.只是一个评论 - 这个hack可能会被更可读和更明显的构造所取代:( a[sizeof a / sizeof a[0] - 1]当然它应该仅应用于数组,而不应用于指针).

  • @trentcl我当然不会为这些不必要的指针杂技辩护,所以对我来说,这些结构中的UB越多越好!那说......关于任意指针的"一个接一个结束":http://stackoverflow.com/a/14505930/2757035"5.7(4)[...]说:"就这些运营商而言,**指向非阵列对象的指针与指向长度为1的数组的第一个元素的指针的行为相同**对象的类型作为其元素类型."所以,仍然不值得做,但看起来合法它. (2认同)
  • 这个答案应该阐明`*(&a + 1)`会导致不确定的行为。(它确实正确地指出了随后无法取消引用`&a + 1`,但是基于对其他答案的注释,一些读者没有意识到`*(&a + 1)`会引用`&a + 1`。) (2认同)