自C++ 17以来,具有正确地址和类型的指针仍然始终是有效指针吗?

Oli*_*liv 80 c++ pointers language-lawyer c++14 c++17

(参考这个问题和答案.)

在C++ 17标准之前,[basic.compound]/3中包含以下句子:

如果类型T的对象位于地址A,则类型为cv T*的指针(其值为地址A)被称为指向该对象,而不管该值是如何获得的.

但是自从C++ 17以来,这句话已被删除.

例如,我相信这句话使这个示例代码定义,并且从C++ 17开始这是未定义的行为:

 alignas(int) unsigned char buffer[2*sizeof(int)];
 auto p1=new(buffer) int{};
 auto p2=new(p1+1) int{};
 *(p1+1)=10;
Run Code Online (Sandbox Code Playgroud)

在C++ 17之前,p1+1保持地址*p2并具有正确的类型,因此*(p1+1)是指向*p2.在C++中,17 p1+1是一个指向前端指针,所以它不是指向对象指针,我相信它不是可以解除引用的.

对标准权的这种修改的解释是否还有其他规则来补偿所引用的句子的删除?

Bar*_*rry 43

对标准权的这种修改的这种解释是否还有其他规则来补偿删除这一引用的句子?

是的,这种解释是正确的.超过末尾的指针不能简单地转换为恰好指向该地址的另一个指针值.

新的[basic.compound]/3说:

指针类型的每个值都是以下之一:
(3.1)指向对象或函数的指针(指针指向对象或函数),或
(3.2)指针通过对象的末尾([expr] .add]),或

那些是相互排斥的.p1+1是指向结尾的指针,而不是指向对象的指针.p1+1指向x[1]一个大小为1的数组的假设p1,而不是p2.这两个对象不是指针可互换的.

我们还有非规范性说明:

[注意:超过对象末尾的指针([expr.add])不被视为指向可能位于该地址的对象类型的无关对象.[...]

这澄清了意图.


正如TC在许多评论中指出的那样(特别是这个),这实际上是尝试实现的问题的一个特例std::vector- 这[v.data(), v.data() + v.size())需要是一个有效的范围而vector不是创建一个数组对象,所以只有定义的指针算法才能从向量中的任何给定对象传递到其假设的一维数组的过去.更多资源,参见CWG 2182,本次讨论,以及关于该主题的论文的两次修订:P0593R0P0593R1(具体 1.3节).

  • @TC你能指点一下我能读到这个"矢量可实现性问题"吗? (5认同)
  • 该示例基本上是已知"矢量"可实现性问题的特殊情况.+1. (3认同)
  • @Oliv自C++ 03以来一直存在一般情况.根本原因是指​​针算法没有按预期工作,因为您没有数组对象. (2认同)

Ser*_*sta 8

在你的例子中,*(p1 + 1) = 10;应该是UB,因为它超过了大小为1 的数组的末尾.但是我们在这里是一个非常特殊的情况,因为数组是在一个更大的char数组中动态构造的.

动态对象创建在4.5 C++对象模型[intro.object],n4659 C++标准草案的§3中描述:

3如果在与"N unsigned char数组"类型或"n std :: byte数组"(21.2.1)类型的另一个对象e关联的存储中创建了一个完整对象(8.3.4),该数组提供存储对于创建的对象if:
(3.1) - e的生命周期已经开始但未结束,
(3.2) - 新对象的存储完全适合e,并且
(3.3) - 没有更小的数组对象满足这些限制.

3.3似乎相当不清楚,但下面的例子使意图更加明确:

struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
A a;
B *b = new (a.a + 8) B; // a.a provides storage for *b
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)
Run Code Online (Sandbox Code Playgroud)

因此,在该示例中,buffer阵列提供存储两个*p1*p2.

下面的段落证明,两个完整的对象*p1,并*p2buffer:

4对象a嵌套在另一个对象b中,如果:
(4.1) - a是b的子对象,或者
(4.2) - b为a提供存储,或者
(4.3) - 存在一个对象c,其中a嵌套在c中,c嵌套在b中.

5对于每个对象x,有一个称为x的完整对象的对象,确定如下:
(5.1) - 如果x是一个完整的对象,则x的完整对象本身.
(5.2) - 否则,x的完整对象是包含x的(唯一)对象的完整对象.

一旦确定,用于C++ 17的n4659草案的其他相关部分是[basic.coumpound]§3(强调我的):

3 ...指针类型的每个值都是下列之一:
(3.1) - 指向对象或函数的指针(指向指向对象或函数的指针),或
(3.2) - 指向结束的指针对象(8.7)或
(3.3) - 该类型的空指针值(7.11),或
(3.4) - 无效指针值.

指针类型的值是指向或超过对象末尾的指针,表示对象占用的内存(4.4)中的第一个字节的地址,或者 对象占用的存储空间结束后的内存中的第一个字节的地址, 分别.[注意:超过对象末尾的指针(8.7)不被视为指向可能位于该地址的对象类型的无关对象.当指示的存储达到其存储持续时间的末尾时,指针值变为无效; 见6.7.-end note]为了指针运算(8.7)和比较(8.9,8.10),超过n个元素的数组x的最后一个元素末尾的指针被认为等同于指向假设元素x的指针[ N].指针类型的值表示是实现定义的.布局兼容类型的指针应具有相同的值表示和对齐要求(6.11)...

该说明过去的结束指针......因为对象指向在这里并不适用p1p2无关系,但嵌套到同一个完整的对象,所以指针算术使提供的存储对象内部的感觉:p2 - p1被定义为(&buffer[sizeof(int)] - buffer]) / sizeof(int)那是1.

所以p1 + 1 指向*p2,并*(p1 + 1) = 10;已定义行为并设置值的指针*p2.


我还阅读了关于C++ 14和当前(C++ 17)标准之间兼容性的C4附录.删除在单个字符数组中动态创建的对象之间使用指针算术的可能性将是一个重要的变化,恕我直言应该在那里引用,因为它是一个常用的功能.由于兼容性页面中没有任何内容,我认为它确认标准的目的不是禁止它.

特别是,它会破坏一个没有默认构造函数的类的对象数组的常见动态构造:

class T {
    ...
    public T(U initialization) {
        ...
    }
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem); // See the array as an array of N T
for (i=0; i<N; i++) {
    U u(...);
    new(arr + i) T(u);
}
Run Code Online (Sandbox Code Playgroud)

arr 然后可以用作指向数组第一个元素的指针......

  • @MatthieuM.见[核心问题2182](https://wg21.link/CWG2182),[this](https://groups.google.com/a/isocpp.org/d/msg/std-discussion/p4BXNhTHY7U/ahp5CzNFQQAJ) std-discussion thread,[P0593R0](https://wg21.link/P0593R0)和[P0593R1(特别是1.3节)](https://wg21.link/P0593R1#dynamic-construction-of-arrays).基本问题是`vector`没有(也不能)创建一个数组对象,但有一个接口允许用户获得一个支持指针算术的指针(仅为指向数组对象的指针定义). (5认同)
  • @TC:Google在查找有关此"矢量可实现性问题"的任何信息时非常无益,您能帮忙吗? (3认同)
  • 你在一个非规范的注释中抓住一个单词"无关",赋予它一个不能承受的含义,这与[expr.add]中指令算法的规范性规则相矛盾.附件C中没有任何内容,因为通用情况指针算法从未在任何标准中起作用.没有什么可以打破的. (2认同)