安全派生指针值的示例

Example of safely derived pointer value

本文关键字:派生 指针 安全      更新时间:2023-10-16

我使用N3797工作草案。

Section 5.7/1规定:

[…对于加法,两个操作数都必须为算术或非作用域枚举类型,或者一个操作数必须是指向对象的指针完全定义的对象类型,其他类型必须具有整型或无作用域枚举类型。

Ok。但是考虑一下3.7.4.3/3节中的规则:

—加法运算或位运算的结果,其中之一操作数是安全派生指针的整数表示形式值P,如果经过reinterpret_cast<void*>转换的结果将比较等于可从中计算的安全派生指针reinterpret_cast<void*>(P) .

也就是说,我们不能对指向void的指针应用指针算术。你能提供一个反映3.7.4.3规则的例子吗?

类似的问题并没有提供一个合适的例子,因为这里指向void算术器的指针

3.7.4.3/3所说的是,如果你有一个"安全派生指针的整数表示",并对它进行数学运算,如果你可以单独使用指针算术和强制转换得到相同的结果,那么结果只是一个有效的"安全派生指针的整数表示"。

虽然不允许直接对void*进行算术操作,但是有各种其他方法可以从void*获得有效的指针。虽然我懒得用标准中的引号来支持每一步,但是下面的例子应该是有效的:

double arr[10];
double* P = &arr[0]; // safely derived pointer
intptr_t N = reinterpret_cast<intptr_t>(P); // valid integer repr
void* V = reinterpret_cast<void*>(P);
// Compute &a[1] from V
void* V2 = reinterpret_cast<void*>(
    static_cast<char*>(V) + sizeof(double));
// Do the same with N
intptr_t N2 = N + sizeof(double);
assert(reinterpret_cast<void*>(N2) == V2);
  • V2是一个安全派生的指针,可从reinterpret_cast<void*>(P)
  • 计算
  • N2是加性或位操作的结果,其中一个操作数(N)是安全派生的指针值P的整数表示形式

由于V2和N2比较相等,因此N2也是"安全派生指针的整数表示"。

垃圾收集实现需要跟踪指针。在实践中,这通常是通过使用内存映射作为键来实现的,因此内存中在特定数字范围内的任何指针大小的单词都被视为真正的指针,这样可以防止包括给定地址值在内的分配块的垃圾收集。这种策略是不精确的,c++的目标是做得更好,同时也支持现有的GC程序。

与你的直觉相反,项目符号确实有效地启用了"void*上的指针算术"。reinterpret_cast从指针获得的整数(std::intptr_t)也必须作为指针被跟踪,并且对象继续影响垃圾收集,即使它被加法或按位操作修改。

std::uintptr_t handle1 = reinterpret_cast< std::uintptr_t >( new foo );
std::uintptr_t handle2 = reinterpret_cast< std::uintptr_t >( new foo );
// handle1 and handle2 won't be collected, despite no pointer-type references.
std::uintptr_t diff = handle2 - handle1; // not a pointer
std::uintptr_t sum = handle1 + diff; // sum refers to handle2
handle2 = 0; // Invalidate original reference to handle2
// At this point, the second foo is still reachable via sum.

严格的指针安全性改善了现状,但是当满足规则的操作没有生成实际的指针表示时,误报仍然是可能的。例如,从标准文本中不清楚diff是否可跟踪,因为它是使用两个安全派生操作数的差操作的结果,而不是仅使用一个安全派生操作数的加法操作。(无论是否有些无关紧要,假阳性无论如何都会发生。)