在您的示例中,*(p1 + 1) = 10;
应该为UB,因为它在大小为1的数组的末尾一个。但是在这里,我们处于一种非常特殊的情况,因为该数组是在较大的char数组中动态构造的。
动态对象创建在C ++标准的n4659草案的C ++对象模型[intro.object] §3中进行了描述:
3如果在与另一个对象“ e的类型为N个无符号字符的数组”或类型为“ N个std :: byte的数组”(21.2.1)的另一个对象e相关联的存储中创建了完整对象(8.3.4),则该数组将提供存储空间对于创建的对象,如果:
(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;
int *p = new (b->b + 4) int;
因此,在该示例中,buffer
阵列提供存储两个*p1
和*p2
。
下面的段落证明,两个完整的对象*p1
,并*p2
为buffer
:
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。—尾注]出于指针算术(8.7)和比较(8.9,8.10)的目的,超过n个元素的数组x的最后一个元素的末尾的指针被视为等效于指向假设元素x [的指针] n]。指针类型的值表示形式是实现定义的。指向布局兼容类型的指针应具有相同的值表示和对齐要求(6.11)...
该说明指针过去的结束......在这里并不适用,因为对象的指向p1
和p2
不无关系,但嵌套到同一个完整的对象,所以指针算术使物体内部的感觉,提供存储:p2 - p1
定义,是(&buffer[sizeof(int)] - buffer]) / sizeof(int)
那是1。
因此p1 + 1
是的指针*p2
,并*(p1 + 1) = 10;
已定义行为并设置的值*p2
。
我还阅读了有关C ++ 14和当前(C ++ 17)标准之间兼容性的C4附件。消除在单个字符数组中动态创建的对象之间使用指针算法的可能性将是IMHO应该在此处引用的一项重要更改,因为它是常用的功能。由于兼容性页面中没有关于它的内容,我认为它确认该标准不是禁止它的意图。
特别是,它将破坏没有默认构造函数的类中对象数组的常见动态构造:
class T {
...
public T(U initialization) {
...
}
};
...
unsigned char *mem = new unsigned char[N * sizeof(T)];
T * arr = reinterpret_cast<T*>(mem);
for (i=0; i<N; i++) {
U u(...);
new(arr + i) T(u);
}
arr
然后可以用作指向数组第一个元素的指针...