谁能告诉我为什么分配给Class::*
类型的数据成员指针的nullptr
的内部表示为-1对于MSVC, clang和g++?对于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
~(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怪知道他们在做什么选择他们的错误代码:)。
关于为什么编译器在优化时不能使用~(0LLU)
,在调试时不能使用1LLU << 63
:你可以把一些翻译单元编译为优化代码,一些编译为调试代码。它们将遵循不兼容的abi,并且不能连接在一起。