指针算术与两个不同的缓冲区

Ser*_*gey 50 c++ pointers pointer-arithmetic language-lawyer

请考虑以下代码:

int* p1 = new int[100];
int* p2 = new int[100];
const ptrdiff_t ptrDiff = p1 - p2;

int* p1_42 = &(p1[42]);
int* p2_42 = p1_42 + ptrDiff;
Run Code Online (Sandbox Code Playgroud)

现在,标准保证p2_42指向p2[42]哪个?如果没有,在Windows,Linux或webassembly堆上总是如此吗?

Max*_*hof 54

要添加标准报价:

expr.add#5

当两个指针表达式PQ相减,其结果的类型是一个实现定义符号整型; 此类型应std::ptrdiff_­t<cstddef>标题([support.types])中定义的类型相同.

  • (5.1)如果PQ两者都计算为空指针值,则结果为0.

  • (5.2)否则,如果PQ分别指向元素x[i]x[j]同一个数组对象x,则表达式P - Q具有值i?j.

  • (5.3)否则,行为未定义.[  注意:如果值i?j不在类型的可表示值范围内std::ptrdiff_­t,则行为未定义. - 结束说明]

(5.1)不适用,因为指针不是nullptrs.(5.2)不适用,因为指针不在同一个数组中.所以,我们留下了(5.3) - UB.

  • @ sudorm-rfslash:危险的领土.数组是对象,但分配器只创建存储而不是对象.这两个数组是两个不同的对象.在这两者之间,无论使用何种分配器,实现都可以为其自身的开销保留空间.通常,实现存储要销毁的元素数量.(有一些标准争论,阵列如何正式逐个元素地增长,但这主要是一个`std :: vector`的东西.`new [100]`是一次性操作) (8认同)
  • 5.2如果你有一个特殊的分配器(我认为)可能适用 (4认同)
  • @ sudorm-rfslash 5.2甚至不适用于多维数组的2个不同子数组(一个完整对象的子对象)(例如`int a [2] [3];&a [1] [0] - &a [0] [2 ];`是UB)并且你希望它适用于在同一个缓冲区中创建2个完整数组对象的情况(例如`unsigned char`数组)... (4认同)
  • @Joker_vD:这不能保证有意义.`uintptr_t`有足够的位来保存指针值,就是这样. (3认同)

Mat*_*lia 28

const ptrdiff_t ptrDiff = p1 - p2;
Run Code Online (Sandbox Code Playgroud)

这是未定义的行为.两个指针之间的减法只有在它们指向同一数组中的元素时才能很好地定义.([expr.add]5.3).

当两个指针表达式PQ相减,其结果的类型是一个实现定义符号整型; 此类型应std::ptrdiff_­t<cstddef>标题([support.types])中定义的类型相同.

  • 如果PQ两者都计算为空指针值,则结果为0.
  • 否则,如果P和Q分别指向元素x[i]x[j]同一个数组对象x,则表达式P - Q具有该值i?j.
  • 否则,行为未定义

即使有一些假设的方法以合法的方式获得这个值,即使这个求和也是非法的,因为即使指针+整数求和也被限制在数组的边界内([expr.add]4.2)

J具有整数类型的表达式添加到P指针类型的表达式或从指针类型的表达式中减去时,结果的类型为P.

  • 如果P求值为空指针值并J求值为0,则结果为空指针值.
  • 否则,如果P指向元件x[i]阵列对象的x与n个元素,81个的表达P + JJ + P(其中,J具有值j)点到(可能-假设的)元件x[i+j],如果0?i+j?n和表达P - J指向(可能-假设的)元件x[i?j],如果0?i?j?n.
  • 否则,行为未定义.

  • @Vaelus这样可以更容易地编写在每一步增加指针的循环.例如,否则`for(char*x = xs; x <(xs + sizeof(xs)); x ++){...}`将是非法的,因为它在中止之前将x递增到其数组的末尾. (4认同)
  • @amalloy _would是非法的,因为它在中止之前将x递增到其数组的末尾_在第一次递增之前它将变为非法 - 在`xs + sizeof(xs)`中. (4认同)
  • @MaxLanghof:AFAICT LanguageLawyer只是说,_if`xs + sizeof(xs)`是非法的(但它不是),你甚至只是在第一次评估条件时得到UB,就在递增之前,因为它在那里第一次评估`xs + sizeof(xs)`子表达式.话虽这么说,如上所示,明确允许创建一个指向"一个接一个"元素的指针(只要你不取消引用它),这是常见的习惯用法. (3认同)

MSa*_*ers 9

第三行是未定义行为,因此标准允许之后的任何内容.

减去指向同一数组(或之后)的两个指针是合法的.

Windows或Linux并不真正相关; 编译器,特别是它们的优化器是打破你的程序的原因.例如,优化器可能会识别出这一点,p1并且p2两者都指向int[100]so 的开头,因此p1-p2必须为0.

  • 由于第三行是未定义的行为,标准允许任何_before_以及:( (3认同)

sup*_*cat 7

该标准允许在平台上实现,其中存储器被划分为使用指针算法不能彼此到达的离散区域.举一个简单的例子,一些平台使用24位地址,由一个8位的bank号和一个16位的地址组成.添加一个标识银行的最后一个字节将产生一个指针的第一个字节的地址相同的银行,而不是第一个字节的下一个银行.这种方法允许使用16位数学而不是24位数学计算地址算术和偏移,但要求没有对象跨越存储体边界.这样的设计会增加一些额外的复杂性malloc,并且可能导致比其他情况更多的存储器碎片,但是用户代码通常不需要关心将存储器划分成存储体.

许多平台没有这样的架构限制,并且一些专为此类平台上的低级编程而设计的编译器将允许在任意指针之间执行地址算法.该标准指出,处理未定义行为的一种常见方法是"在文档中以环境特征的方式执行转换或程序执行",并且在支持它的环境中对广义指针算法的支持将很好地适合该类别.遗憾的是,该标准未能提供任何区分以这种有用方式表现的实现方式和不提供方式的方法.