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

时间:2016-07-05 11:16:55

标签: c pointers

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

#include <stdio.h>

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

    printf("%d\n", *(*(&a + 1) - 1));
    return 0;
}

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

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

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

  1. &a + 1,已在SO中询问:about the expression “&anArray” in c

  2. *(&a + 1) -1,与数组衰减有关。

7 个答案:

答案 0 :(得分:35)

首先提醒一下(如果你以前不知道这个,还是新的东西):对于任何数组或指针p和索引i,表达式p[i]*(p + i)

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

程序中的数组a存储在内存中的某个位置,这实际上并不重要。要获取存储a的位置,即获取指向a的指针,请使用&之类的地址操作符&a。这里要学习的重要一点是,指针本身并不意味着什么特别,重要的是指针的基类型a的类型为int[4],即a是包含四个int元素的数组。表达式&a的类型是指向四个intint (*)[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]也必须是此类型)四个数组{{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,得到{{1}之前的元素这是(&a)[1]中的最后一个元素,即它为(&a)[1]提供了与(&a)[0]相同的内容。

所以表达式(&a)[0][3]a[3]相同。

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

答案 1 :(得分:12)

让我们剖析它。

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)开始之前开始*(&a + 1)个字节的int。这是与&a[3]相同的指针值。

*(*(&a + 1) - 1)的类型为int。由于*(&a + 1) - 1&a[3]的指针值相同,因此*(*(&a + 1) - 1)相当于a[3],已初始化为3,因此这是由printf

答案 2 :(得分:7)

&a + 1会在a元素之后立即指向内存,或者更好地指向a数组之后的内存,因为&a的类型为int (*)[4](指针)到四个int的数组。标准允许构造此类指针,但不允许解除引用。结果你可以将它用于随后的算术。

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

答案 3 :(得分:2)

最好向自己证明:

$ cat main.c
#include <stdio.h>
main()
{
  int a[4];
  printf("a    %p\n",a);
  printf("&a   %p\n",&a);
  printf("a+1  %p\n",a+1);
  printf("&a+1 %p\n",&a+1);
}

以下是地址:

$ ./main
a    0x7fff81a44600 
&a   0x7fff81a44600 
a+1  0x7fff81a44604
&a+1 0x7fff81a44610

前两个是相同的地址。第三个是4以上(sizeof(int))。第4个是0x10 = 16以上(sizeof(a)

答案 4 :(得分:1)

如果您有T类型的对象,例如

T obj;

然后声明

T *p = &obj;

使用对象占用的内存地址p初始化指针obj

表达式p + 1指向对象obj之后的内存。表达式p + 1的值等于&obj plus sizeof( obj )的值,等于

( T * )( ( char * )&obj + sizeof( obj ) )

因此,如果您在帖子int a[] = {0, 1, 2, 3};中显示了数组,则可以使用typedef以下列方式重写其声明:

typedef int T[4];

T a = { 0, 1, 2, 3 };
在这种情况下,

sizeof( T )等于sizeof( int[4] ),反过来等于4 * sizeof( int )

表达式&a给出了数组占用的内存范围的地址。表达式&a + 1给出数组后面的内存地址,表达式的值等于&a + sizeof( int[4] )

另一方面,表达式中使用的数组名称 - 极少数例外,例如在sizeof运算符中使用数组名称 - 被隐式转换为指向其第一个元素的指针。

因此,表达式&a + 1指向真实的第一个元素int[4]之后的a类型的想象元素。表达式*(&a + 1)给出了这个想象的元素。但由于该元素是一个类型为int[4]的数组,因此该表达式将转换为指向其第一个int *类型元素的指针

第一个元素跟在数组a的最后一个元素之后。在这种情况下,表达式*(&a + 1) - 1给出了数组a的最后一个元素的地址

通过*(*(&a + 1) - 1)中的解除引用,您可以获得数组a的最后一个元素的值,因此将输出数字3

答案 5 :(得分:0)

请注意,以下内容相同,但同样令人讨厌:

printf("%d\n", (&a)[1][-1]);

在这种情况下,我认为更明确的是:

指向数组a的指针

  • 指针的使用就好像它是一个数组:一个元素数组 a,即使用4个整数的数组,使用该数组的第1个元素。

  • 由于a实际上不是一个数组,而只是一个元素(由...组成) 四个子元素!)这在

  • 之后直接索引内存
  • [-1]直接读取内存前面的整数 在a之后,这是

  • 的最后一个子元素

答案 6 :(得分:-1)

*(*(&a + 1) - 1)

是解决数组中最后一个元素的一种尴尬和危险的方法。 &amp; a是int [4]类型数组的地址。 (&amp; a + 1)在当前寻址的a之后给出下一个int [4]数组。通过使用*(&amp; a + 1)取消引用它,你可以使它成为* int,而使用附加的-1,你可以指向a的最后一个元素。然后取消引用最后一个元素,从而返回值3(在您的示例中)。

如果数组元素的类型与目标CPU的对齐长度相同,则此方法很有效。考虑你有一个类型为uint8和长度为5的数组的情况:     uint8 ar [] = {1,2,3,4,5}; 如果你现在这样做(在32位架构上),你在5的值之后寻址一个未经填充的填充字节。因此ar [5]的地址与4个字节对齐。 ar中的各个元素与单个字节对齐。即,ar [0]的地址与ar本身的地址相同,ar [1]的地址是ar之后的一个字节(而不是ar之后的4个字节),...,ar的地址[4]是ar加5个字节,因此不与4个字节对齐。如果你这样做(&amp; a + 1)你得到下一个uint8 [5]数组的地址,该数组与4字节对齐,即它是8加8字节。如果你取这个ar的地址加上8个字节并返回一个字节,你就读到了ar加7,它没有被使用。