C++17中地址与类型正确的指针是否始终有效?标准修改解读咨询
C++17标准修改的解读与补充规则说明
你的解读完全正确,咱们来详细拆解这个问题,同时看看有没有其他规则能覆盖原来语句的部分场景:
一、C++17之前的规则逻辑
在C++17之前,[basic.compound]/3的那条规则是核心:只要指针的值是某个对象的地址,且指针的cv-qualified类型和对象类型匹配,不管这个指针是怎么来的,都被认为指向该对象。
放到你的示例代码里:
alignas(int) unsigned char buffer[2*sizeof(int)]; auto p1=new(buffer) int{}; auto p2=new(p1+1) int{}; *(p1+1)=10;
p1指向buffer中第一个int对象,p1+1的地址刚好是p2指向的第二个int对象的地址,而且p1+1的类型是int*,和p2指向的对象类型完全匹配。按照旧规则,*(p1+1)完全合法,行为是确定的——就是修改p2指向的那个int对象的值。
二、C++17之后的语义变化
C++17移除了这条规则,核心的语义变化在于:指针的“指向”不再单纯由地址和类型决定,还要看指针的来源和语义。
在你的示例里,p1是指向单个int对象的指针(不是数组元素的指针),所以p1+1是这个单个int对象的尾后指针。根据C++17及之后的规则,尾后指针只能用于比较(比如和其他指针比较地址),绝对不能被解引用——哪怕它的地址刚好对应另一个合法对象,解引用操作依然属于未定义行为。
这时候哪怕p1+1的地址和p2完全重合,编译器也不认为p1+1指向p2的对象,因为p1+1的语义是“第一个int的尾后”,而非“第二个int的起始”。
三、有没有其他规则弥补?
虽然那条规则被移除了,但C++标准里还有一些相关规则可以处理类似的场景,但它们不是直接替代,而是有严格的适用条件:
- 对象存储位置的指针转换:如果一个指针指向某个存储位置,而该位置已经通过placement new创建了兼容类型的对象,你可以通过
reinterpret_cast将该存储位置的指针(比如unsigned char*类型的buffer指针)转换为int*来访问对象。比如你可以直接用reinterpret_cast<int*>(buffer)+1来访问第二个int,这是合法的。 std::launder的适用场景:如果原来的指针指向的对象已经结束生命周期,且在同一地址创建了新对象,std::launder可以让指针指向新对象。但在你的示例里,p1指向的对象还活着,所以std::launder在这里不适用。- 数组语义的指针:如果你一开始就把buffer当作int数组的存储(比如
int* arr = reinterpret_cast<int*>(buffer);),那么arr+1就是数组第二个元素的指针,解引用完全合法——因为这时候指针的语义是数组元素的指针,而非单个对象的尾后指针。
要注意的是,这些规则都是有边界的,不能像旧规则那样“只要地址和类型对就合法”,而是需要严格符合指针的语义来源。
内容的提问来源于stack exchange,提问作者Oliv
相关产品推荐
相关产品推荐

