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

Pointer arithmetics with two different buffers

考虑以下代码:

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 堆上总是正确的吗?

const ptrdiff_t ptrDiff = p1 - p2;

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

When two pointer expressions P and Q are subtracted, the type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined as std::ptrdiff_­t in the <cstddef> header ([support.types]).

  • If P and Q both evaluate to null pointer values, the result is 0.
  • Otherwise, if P and Q point to, respectively, elements x[i] and x[j] of the same array object x, the expression P - Q has the value i−j.
  • Otherwise, the behavior is undefined

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

When an expression J that has integral type is added to or subtracted from an expression P of pointer type, the result has the type of P.

  • If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.
  • Otherwise, if P points to element x[i] of an array object x with n elements,81 the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i+j] if 0≤i+j≤n and the expression P - J points to the (possibly-hypothetical) element x[i−j] if 0≤i−j≤n.
  • Otherwise, the behavior is undefined.

第三行是未定义行为,因此标准允许此后的任何行为。

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

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

要添加标准报价:

expr.add#5

When two pointer expressions P and Q are subtracted, the type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined as std::ptrdiff_­t in the <cstddef> header ([support.types]).

  • (5.1) If P and Q both evaluate to null pointer values, the result is 0.

  • (5.2) Otherwise, if P and Q point to, respectively, elements x[i] and x[j] of the same array object x, the expression P - Q has the value i−j.

  • (5.3) Otherwise, the behavior is undefined. [ Note: If the value i−j is not in the range of representable values of type std::ptrdiff_­t, the behavior is undefined. — end note  ]

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

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

许多平台没有这样的体系结构限制,一些专为在此类平台上进行低级编程而设计的编译器将允许在任意指针之间执行地址运算。该标准指出,处理未定义行为的一种常见方法是 "behaving during translation or program execution in a documented manner characteristic of the environment",并且在支持它的环境中支持广义指针算法非常适合该类别。不幸的是,该标准未能提供任何方法来区分以这种有用方式运行的实现和不以这种方式运行的实现。