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)
3, 93, 12, 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\nRun Code Online (Sandbox Code Playgroud)\n该&运算符获取数组的地址,a为我们提供一个类型的表达式int(*)[5],即指向int大小为 5 的数组的指针。向此添加 1 会将指针视为指向 数组的第一个元素int [5],结果指针仅指向后a。
此外,即使&a指向单个对象(在本例中是类型为 的数组int [5]),我们仍然可以向该地址加 1。这是有效的,因为 1) 指向单个对象的指针可以被视为指向大小为 1 的数组的第一个元素的指针,并且 2) 指针可以指向超出数组末尾的一个元素。
C 标准第 6.5.6p7 节规定了以下有关将指向对象的指针视为指向大小为 1 的数组的第一个元素的指针的信息:
\n\n\n就这些运算符而言,指向不是数组元素的对象的指针的行为与指向长度为 1 的数组的第一个元素的指针相同,且对象的类型作为其元素类型。
\n
第 6.5.6p8 节介绍了以下关于允许指针指向数组末尾的内容:
\n\n\n当具有整数类型的表达式与指针相加或相减时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素的偏移量,使得结果数组元素和原始数组元素的下标之差等于\n 整数表达式。换句话说,如果表达式 P 指向数组对象的\n第 i个元素,则表达式
\n(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(int *)(&a + 1)\nRun Code Online (Sandbox Code Playgroud)\n这会将 type 的指针转换int(*)[5]为 type int *。这里的目的是将指向 1 元素数组末尾的指针更改int [5]为 5 元素数组末尾int。
然而,C 标准并不清楚是否允许这种转换以及对结果的后续操作。假设指针正确对齐,它确实允许从一种对象类型转换为另一种对象类型并返回。虽然对齐不应该成为问题,但使用这个指针是不确定的。
\n所以这个指针被分配给p:
int *p = (int *)(&a + 1)\nRun Code Online (Sandbox Code Playgroud)\n然后使用如下:
\n*(p - 1)\nRun Code Online (Sandbox Code Playgroud)\n如果我们假设p有效地指向数组末尾之后的一个元素a,则从中减去 1 会得到指向数组最后一个元素的指针。然后该*运算符取消引用该指针到最后一个元素,产生值 9。
因此,如果我们假设(int *)(&a + 1)结果是一个有效的指针,那么答案是 1) 3,9,否则答案是 2) 错误。
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。
通常,只要在表达式中使用数组,它们就会“衰减”为指向第一个元素的指针。此规则有一些例外,其中一个例外是&运算符。
&a因此产生一个指向类型数组的指针int (*)[5]。然后&a + 1是对这种类型的指针算术,这意味着指针地址增加 1 的大小int [5]。我们最终指向了数组之外,但 C 实际上允许我们这样做,只要我们不取消引用该位置即可。
然后,指针被强制进行类型转换,(int *)我们也可以这样做 - 只要我们不取消引用或导致错位等,C 就允许几乎任何方式的野指针转换。
p - 1对类型进行指针算术int,并且数组中数据的实际类型也是int,因此我们可以取消对该位置的引用。我们最终到达数组的最后一项。