为什么这段涉及数组和指针的代码会有这样的行为?

Cod*_*ron 34 c language-lawyer

有人问我以下代码的输出是什么:

int a[5] = { 1, 3, 5, 7, 9 };
int *p = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(p - 1));
Run Code Online (Sandbox Code Playgroud)
  1. 3, 9
  2. 错误
  3. 3, 1
  4. 2, 1

答案是NO.1


很容易得到的*(a+1)是3。

但是 和int *p = (int *)(&a + 1);*(p - 1)

dbu*_*ush 39

答案可能是“1)3,9”或“2)错误”(或更具体地说是未定义的行为),具体取决于您如何阅读C 标准

\n

首先,我们来看一下:

\n
&a + 1\n
Run Code Online (Sandbox Code Playgroud)\n

&运算符获取数组的地址,a为我们提供一个类型的表达式int(*)[5],即指向int大小为 5 的数组的指针。向此添加 1 会将指针视为指向 数组的第一个元素int [5],结果指针仅指向后a

\n

此外,即使&a指向单个对象(在本例中是类型为 的数组int [5]),我们仍然可以向该地址加 1。这是有效的,因为 1) 指向单个对象的指针可以被视为指向大小为 1 的数组的第一个元素的指针,并且 2) 指针可以指向超出数组末尾的一个元素。

\n

C 标准第 6.5.6p7 节规定了以下有关将指向对象的指针视为指向大小为 1 的数组的第一个元素的指针的信息:

\n
\n

就这些运算符而言,指向不是数组元素的对象的指针的行为与指向长度为 1 的数组的第一个元素的指针相同,且对象的类型作为其元素类型。

\n
\n

第 6.5.6p8 节介绍了以下关于允许指针指向数组末尾的内容:

\n
\n

当具有整数类型的表达式与指针相加或相减时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素的偏移量,使得结果数组元素和原始数组元素的下标之差等于\n 整数表达式。换句话说,如果表达式 P 指向数组对象的\n第 i个元素,则表达式(P)+N\n( 等效地,N+(P)) 和(P)-N(其中N值为n ) 分别指向\n第i+n 个元素和数组对象的i\xe2\x88\x92n个元素,\n前提是它们存在。此外, 如果表达式P指向数组对象的最后一个元素,则该表达式(P)+1指向数组对象的最后一个元素,如果表达式Q指向数组对象的最后一个元素,则表达式指向数组对象的最后一个(Q)-1元素到数组\n对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出;否则,行为是未定义的。如果结果指向\n超过数组对象的最后一个元素,则不应将其用作\n一元的操作数*计算的一元运算符的操作数。

\n
\n

现在是有问题的部分,那就是演员阵容:

\n
(int *)(&a + 1)\n
Run Code Online (Sandbox Code Playgroud)\n

这会将 type 的指针转换int(*)[5]为 type int *。这里的目的是将指向 1 元素数组末尾的指针更改int [5]为 5 元素数组末尾int

\n

然而,C 标准并不清楚是否允许这种转换以及对结果的后续操作。假设指针正确对齐,它确实允许从一种对象类型转换为另一种对象类型并返回。虽然对齐不应该成为问题,但使用这个指针是不确定的。

\n

所以这个指针被分配给p

\n
int *p = (int *)(&a + 1)\n
Run Code Online (Sandbox Code Playgroud)\n

然后使用如下:

\n
*(p - 1)\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们假设p有效地指向数组末尾之后的一个元素a,则从中减去 1 会得到指向数组最后一个元素的指针。然后该*运算符取消引用该指针到最后一个元素,产生值 9。

\n

因此,如果我们假设(int *)(&a + 1)结果是一个有效的指针,那么答案是 1) 3,9,否则答案是 2) 错误。

\n

  • @LanguageLawyer `offsetof` 是标准的一部分,所以从用户的角度来看它如何做并不重要。 (9认同)
  • 在没有填充的情况下,“sizeof(int[5])”是否保证等于“sizeof(int)*5”?如果没有,那就是另一种失败模式 (3认同)

And*_*zel 24

在行

int *p = (int *)(&a + 1);

请注意,&a正在写入,而不是a。这个很重要。

如果只是简单地a写入,那么该数组将衰减为指向第一个元素的指针,即指向&a[0]。但是,由于使用了表达式,因此&a该表达式的结果与使用的值相同,但类型不同:类型是指向 5 个元素的数组的指针,而不是指向单个元素的指针元素。a&a[0]intint

根据指针算术规则,将指针递增1将使内存地址增加它所指向的对象的大小。由于指针不是指向单个元素,而是指向 5 个元素的数组,因此内存地址将增加5 * sizeof(int)。因此,在递增指针之后,指针的值(而不是类型)将等于&a[5],即超出数组末尾的一位。

将 this 指针强制转换int *并将结果赋给 后p,表达式p完全等价于&a[5](无论是在值上还是在类型上)。

因此,该表达式*(p - 1)相当于*(&a[5] - 1),相当于*(&a[4]),或者简单地相当于a[4]


unw*_*ind 13

这:

&a + 1;
Run Code Online (Sandbox Code Playgroud)

获取a数组 的地址,然后加 1,这会增加 1 的大小a,即 5 个整数。然后索引“后退”一个整数,最终到达 的最后一个元素a


Lun*_*din 7

通常,只要在表达式中使用数组,它们就会“衰减”为指向第一个元素的指针。此规则有一些例外,其中一个例外是&运算符。

&a因此产生一个指向类型数组的指针int (*)[5]。然后&a + 1是对这种类型的指针算术,这意味着指针地址增加 1 的大小int [5]。我们最终指向了数组之外,但 C 实际上允许我们这样做,只要我们不取消引用该位置即可。

然后,指针被强制进行类型转换,(int *)我们也可以这样做 - 只要我们不取消引用或导致错位等,C 就允许几乎任何方式的野指针转换。

p - 1对类型进行指针算术int,并且数组中数据的实际类型也是int,因此我们可以取消对该位置的引用。我们最终到达数组的最后一项。