为什么*(pa-1)的输出是`a`的最后一个元素?

4
我从以下代码的输出中得到的是*(pa-1)=5,为什么会这样呢?
#include<iostream>

using namespace std;

int main(){

    int a[5]={1,2,3,4,5};
    int *pa=(int *)(&a+1);

    cout<<"*(pa-1)="<<*(pa-1)<<endl;

}

很确定这是未定义行为,因为你将指向int数组的指针转换为指向int的指针。 - Dan F
只要 T 是标准布局,(T*)[N] 可以转换为 T* 并且可以返回第一个元素的地址。 - Kerrek SB
2个回答

12

&a是数组的地址,类型为“指向int[5]的指针”。因此&a + 1将整个由五个元素组成的数组移动一个单位,并指向数组的末尾。

pa是一种类型转换后的指针*,它现在将同一地址视为整数数组(而不是数组!)。因此与指向数组末尾的指针a + 5相同。减一可以获得指向数组最后一个元素的指针,即5

*) 只要数组的基础类型是标准布局的,例如int,这种类型的重解释都是可接受的,而且会按照你所期望的方式运行。


3
数组很特殊:a 是其自身的地址,而 &a == a,尽管它们是不同类型。非常好的写作。 - Richard Sitze
@HristoIliev:我不认为Richard在抱怨。他只是指出数组在某种意义上是独特的,因为变量名本身可以作为对象地址的地址,尽管类型不同。不过他可能应该说(void*)(a) == (void*)(&a) :-) - Kerrek SB
@KerrekSB,在他编辑评论之前,这似乎是一份投诉。 - Hristo Iliev
感谢Kerrek和所有人的纠正:是的,(void*)(a) == (void*)(&a)是我应该说的。 - Richard Sitze
@RichardSitze:实际上,你应该转换为char *而不是void *。1999年的C标准在6.3.2.3 7中说,将指针转换为字符类型会产生对象的最低地址字节。因此,(char *) a(char *) &a必须产生相同的地址。对于转换为void *,没有这样的保证,因此转换为void *可能会给出不同的值或者在比较时导致未定义的行为。 - Eric Postpischil
显示剩余5条评论

1

你还能期望什么呢?&a 的类型是 int (*)[5],所以 &a + 1 指向了 a 后面的下一个 int[5];它是经典的“结束”迭代器,用于迭代 int[5] 元素。然后你将其 reinterpret_castint*,并访问它。严格来说,我认为你的代码具有未定义行为,因为你可以合法地使用 reinterpret_cast 的结果只有将其转换回原始类型。(当涉及到字符类型的指针时,有一些例外情况。)实际上,在标准中分散的各种要求意味着你将获得 a 中最后一个 int 的地址。


(int*)[5] 不是一个合法的类型表达式。&a 的类型是 int (*)[5],一个指向包含5个int元素的数组的指针。 - James Kanze
类型是int (*)[5]。有关语法,请参阅C99标准的§6.7.6节。 - Hristo Iliev
@KerrekSB 已经修复了:-)。(我想在发布之前可能不小心编辑错了什么。也许是类似于 a 具有类型 int [5],因此 &a 具有类型 int (*)[5] 的内容。) - James Kanze
为了确保,我认为这不是未定义行为(UB),因为当类型是标准布局时,将一个对象的指针视为第一个成员或元素的指针是可以允许的。 - Kerrek SB
@KerrekSB 这在 C++11 中可能已经发生改变。至少在早期,这是未定义的行为:C 标准被精心制作,以允许“fat”指针,其中包括边界信息(以及可能还包括一些类型信息),以便实现完全检查。早期的 C++ 也允许这样做,至少过去有一个编译器实现了它。 - James Kanze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接