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



考虑以下代码:

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堆上总是这样吗?

添加标准报价:

expr.add#5

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

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

  • (5.2)否则,如果PQ分别指向同一阵列对象x的元素x[i]x[j],则表达式P - Q具有值i−j

  • (5.3)否则,行为是未定义的。[注意:如果值i−j不在类型std::ptrdiff_­t的可表示值的范围内,则行为未定义。--尾注]

(5.1)不适用,因为指针不是nullptr。(5.2)不适用,因为指针不在同一数组中。因此,我们只剩下(5.3)-UB。

const ptrdiff_t ptrDiff = p1 - p2;

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

当减去两个指针表达式PQ时,结果的类型是实现定义的有符号积分类型;该类型应与<cstddef>标头中定义的std::ptrdiff_­t类型相同([support.types])

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

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

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

  • 如果P的计算结果为空指针值,而J的计算结果则为0,则结果为空指示器值
  • 否则,如果P指向具有n个元素的数组对象x的元素x[i],则81表达式P + JJ + P(其中J具有值j)如果0≤i+j≤n则指向(可能假设的)元素x[i+j],并且如果0≤i−j≤n则表达式P - J指向(可能假定的)元素x[i−j]
  • 否则,行为是未定义的

第三行是"未定义的行为",因此标准允许在这之后执行任何操作。

只有减去指向(或在)同一数组的两个指针才是合法的。

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

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

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

相关内容

  • 没有找到相关文章

最新更新