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

时间:2019-01-28 11:52:11

标签: c++ pointers language-lawyer pointer-arithmetic

考虑以下代码:

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;

现在,标准是否可以保证p2_42指向p2[42]?如果不是,在Windows,Linux或Webassembly堆上是否总是如此?

4 个答案:

答案 0 :(得分: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。

答案 1 :(得分:28)

const ptrdiff_t ptrDiff = p1 - p2;

这是未定义的行为。仅当两个指针指向同一数组中的元素时,它们的减法定义良好。 ([expr.add] ¶5.3)。

  

当减去两个指针表达式PQ时,结果的类型为实现定义的带符号整数类型;此类型应与在std::ptrdiff_­t标头中定义为<cstddef>的类型([support.types])相同。

     
      
  • 如果PQ都得出空指针值,则结果为0。
  •   
  • 否则,如果P和Q分别指向同一数组对象x[i]的元素x[j]x,则表达式P - Q的值为{{1 }}。
  •   
  • 否则,行为是不确定的
  •   

即使有某种假想的方式以合法的方式获取该值,即使求和也是非法的,因为即使指针+整数求和也被限制在数组的边界内([expr.add] ¶4.2

  

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

     
      
  • 如果P的值为空指针值,而P的值为0,则结果为空指针值。
  •   
  • 否则,如果J指向具有n个元素的数组对象P的元素x[i],则 81 表达式x和{{ 1}}(其中P + J的值为J + P)指向{可能是假设的)元素J,如果j且表达式x[i+j]指向(可能是假设的)元素0≤i+j≤n,如果P - J
  •   
  • 否则,行为是不确定的。
  •   

答案 2 :(得分:9)

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

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

Windows或Linux并非真正相关;编译器,尤其是其优化器是破坏您程序的原因。例如,优化程序可能会发现p1p2都指向int[100]的开头,因此p1-p2必须为0。

答案 3 :(得分:7)

该标准允许在将内存划分为离散区域的平台上实施,这些区域无法使用指针算法相互访问。举一个简单的例子,某些平台使用24位地址,该地址由一个8位存储体号和一个存储体内的16位地址组成。在标识存储体最后一个字节的地址中添加一个将产生指向该 same 存储体的第一个字节的指针,而不是 next 存储体的第一个字节的指针。这种方法允许使用16位数学而不是24位数学来计算地址算术和偏移量,但是要求没有对象跨越库边界。这样的设计会给malloc带来一些额外的复杂性,并且可能导致更多的内存碎片,而不是其他情况,但是用户代码通常不需要关心将内存划分为存储体的情况。

许多平台没有这样的体系结构限制,并且某些为此类平台上的低级编程而设计的编译器将允许在任意指针之间执行地址算术。该标准指出,处理未定义行为的一种常见方法是“在翻译或程序执行过程中表现为环境的特征文件”,并且在支持它的环境中支持通用指针算术将非常适合该类别。不幸的是,该标准未能提供任何手段来区分以这种有用方式表现的实施方式和没有这种方式的实施方式。