将 nullptr 赋值给成员指针。

9

有没有人能告诉我为什么在MSVC、clang和g++中,将nullptr分配给类型为Class::*的数据成员指针的内部表示为-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
C标准库允许系统为了向后兼容而定义空指针。我很惊讶你能找到一个实际上需要这种情况的案例。你运行在什么类型的系统上? - Locke
1
@drescherjm:我在谈论_member_指针,它们在内部只是偏移量,而不是绝对指针。 - Bonita Montero
3
@Locke:我不是在谈论 C,而是在谈论 C++。C 没有成员指针。 - Bonita Montero
3
@GoswinvonBrederlow "-1"和"~0"是同一个数。 - user253751
1
加载0、-1或(size_t)1 << 63没有速度差异。 - Bonita Montero
显示剩余20条评论
1个回答

4

~(0LLU) 更为可取的原因有三个:

  1. Member pointers can be anything from 0 to the size of the struct or class. Using ~(0LLU) has the least risk of colliding with an actually valid member pointer. You can't really have a struct the size of 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];
    

    Note that limit is (1LLU<<63) - 1. So that kind of negates this argument. Might be different on a 16bit system.

  2. On x86_64 loading a 0, ~(1LLU) and 1LLU << 63 becomes

    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
    

    Loading 0 is the fastest. Loading 1LLU << 63 is the longest opcode and that alone has performance costs. So using ~(0LLU) as the member pointer nullptr has a slight performance advantage.

    It's similar on many architectures. On Mips64 the last needs a whole extra opcode: https://godbolt.org/z/3nehjcoM6

  3. It's customary from the old C days that a function returns -1 or ~(0LLU) as error code except for pointers where 0 is used. Member pointers can't use 0.

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

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


你无法将成员指针强转为任何类型,并且在非优化和优化代码中,其值可能不同。 - Bonita Montero
1
@BonitaMontero 不是的。你可以在TUs中包含相同的头文件,并声明成员指针,将它们传递,将它们与nullptr进行比较。它们必须是兼容的。 - Goswin von Brederlow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接