谁能告诉我为什么分配给 Class::*
类型的数据成员指针的 nullptr
的内部表示对于 MSVC、clang 和 g++ 是 -1?对于 64 位系统,(size_t)1 << 63
是最好的,因为如果你使用 nullptr
成员指针,你肯定会触及内核内存并崩溃,所以这将是一个很好的调试帮助。
-1背后有更深层次的原因吗?
样本:
struct X
{
int x, y;
};
using member_ptr = int X::*;
member_ptr f()
{
return nullptr;
}
... 使用 g++ 生成以下二进制文件:
movq $-1, %rax
ret
回答1
~(0LLU)
更可取的原因有以下三个:
成员指针可以是从 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];
请注意,限制是
(1LLU<<63) - 1
。所以那种否定了这个论点。在 16 位系统上可能会有所不同。在 x86_64 上加载
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
加载
0
是最快的。加载1LLU << 63
是最长的操作码,仅此一项就有性能成本。所以使用~(0LLU)
作为成员指针nullptr
有轻微的性能优势。在很多架构上都是类似的。在 Mips64 上,最后一个需要一个额外的操作码:https://godbolt.org/z/3nehjcoM6
从旧的 C 时代开始,函数返回
-1
或~(0LLU)
作为错误代码是一种习惯,但使用 0 的指针除外。成员指针不能使用 0。
我个人认为编译器开发人员只是遵循旧习惯(原因 3)。它也更快只是运气(或者那些老 C geezers 知道他们在哪里选择错误代码:)。
至于为什么编译器在优化时不能使用~(0LLU)
,在调试时不能使用1LLU << 63
:你可以编译一些翻译单元作为优化代码和一些调试代码。然后,它们将遵循不兼容的 ABI,并且无法链接在一起。