将 nullptr 分配给成员指针

Bon*_*ero 9 c++ pointer-to-member nullptr

谁能告诉我为什么对于 MSVC、clang 和 g++,nullptr分配给类型数据成员指针的内部表示是 -1?Class::*对于 64 位系统来说(size_t)1 << 63是最好的,因为如果您nullptr以这种方式使用成员指针,您肯定会接触内核内存并发生崩溃,因此这将是一个很好的调试辅助工具。

-1背后有更深层次的原因吗?

样本:

struct X
{
    int x, y;
};

using member_ptr = int X::*;

member_ptr f()
{
    return nullptr;
}
Run Code Online (Sandbox Code Playgroud)

...使用 g++ 生成以下二进制文件:

movq    $-1, %rax
ret
Run Code Online (Sandbox Code Playgroud)

Gos*_*low 4

更可取的原因有以下三个~(0LLU)

  1. 成员指针可以是从 0 到结构或类的大小的任何值。使用~(0LLU)与实际有效的成员指针发生冲突的风险最小。你不能真正拥有一个大小为的结构size_t

    <source>:2:21: error: size '9223372036854775808' of array 'x' exceeds maximum object size '9223372036854775807'
        2 |     long long x[1LLU<<63];
    
    <source>:2:15: error: size of array 'x' exceeds maximum object size '9223372036854775807'
        2 |     long long x[1LLU<<62];
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,极限是(1LLU<<63) - 1。所以这就否定了这个论点。在 16 位系统上可能会有所不同。

  2. 在 x86_64 上加载 a 0~(1LLU)1LLU << 63变为

    31 ff                            xor    %edi,%edi
    48 c7 c7 ff ff ff ff             mov    $0xffffffffffffffff,%rdi
    48 bf 00 00 00 00 00 00 00 80    movabs $0x8000000000000000,%rdi
    
    Run Code Online (Sandbox Code Playgroud)

    加载0速度是最快的。加载1LLU << 63是最长的操作码,仅此一项就会带来性能成本。因此使用~(0LLU)作为成员指针nullptr具有轻微的性能优势。

    在许多架构上都是类似的。在 Mips64 上,最后一个需要整个额外的操作码:https ://godbolt.org/z/3nehjcoM6

  3. 从旧的 C 时代开始,函数就习惯将-1or~(0LLU)作为错误代码返回,但使用 0 的指针除外。成员指针不能使用0。

我个人认为编译器开发人员只是遵循旧习惯(原因 3)。它也更快只是运气(或者那些老 C 怪人知道他们在哪里选择他们的错误代码:)。

至于为什么编译器~(0LLU)在优化和1LLU << 63调试时不能使用:您可以将一些翻译单元编译为优化代码,一些翻译单元编译为调试代码。然后,它们将遵循不兼容的 ABI,并且无法链接在一起。