null指针解引用用作左值时

huangapple go评论65阅读模式
英文:

null pointer dereference when used as an lvalue

问题

Background

我有一个包含不同成员(在运行时构造的自定义结构)的类。我还有一个在编译时包含了指向成员元素和字符串对的元组。在编译时,我需要检查每个指向成员和名称是否仅在列表中使用一次,并且自定义结构需要检查它们是否在元组中有一个条目(它们知道自己的指向成员)。为了达到这个目的,使用元组会显著增加编译时间,所以最好能够在编译时使用void*数组来标识成员,而不是使用异构数据结构。

Attempt to solve problem

根据我在这个帖子中的阅读,解引用nullptr并不总是未定义行为。

我还阅读了CWG-issue #315,其中提到:

> 我们同意应该允许这个示例。p->f() 被重写为 (*p).f(),根据5.2.5 [expr.ref]。当p为null时,*p不是错误,除非lvalue被转换为rvalue(根据4.1 [conv.lval]),但在这里并没有被转换。

我想利用这一点来从指向成员获取普通指针(我不想解引用它们,只想比较来自同一类但具有不同类型的成员指针)。

所以我创建了以下代码:

#include <iostream>

class Test
{
    int a;
public:
    static constexpr inline int Test::*memPtr = &Test::a;
    static constexpr inline int* intPtr = &(static_cast<Test*>(nullptr)->*Test::memPtr);
};
    
int main () {
    std::cout << Test::intPtr << std::endl;
}

在我看来,&(static_cast<Test*>(nullptr)->*Test::memPtr); 表达式使用了与CWG-issue #315中讨论的代码相同的方法。

上面的代码在MSVC下编译通过,但在clang或gcc下不通过。

我检查了是否类似的代码在#315中提到是否编译:

struct Test {
  static constexpr int testFun () { return 10; } 
};

int main ()
{
  static constexpr int res{static_cast<Test*>(nullptr)->testFun()};
  static_assert(res == 10, "error");
}

是的,它通过了。测试代码

我在第一个示例中使用的构造是否应该在constexpr表达式中可用(因为那里不允许未定义行为)?


有趣的事实:如果我修改我的原始代码并为类添加一个虚析构函数,那么MSVC和clang都可以接受,而gcc会崩溃。我的意思是真的,它段错误

有趣的事实2:如果我删除虚析构函数并将类模板化,gcc和MSVC可以编译,但现在clang投诉

英文:

Background

I have a class containing different members (custom run time constructed structs). And I have a compile time tuple containing pairs of pointer-to-member elements and strings. Compile time I need to check if every pointer-to-member and name is used only once in the list, and the custom structs check if they have an entry in the tuple (they know their own pointer-to-member). Having a tuple for this purpose increases the compile time dramatically, it would be great to identify the members in compile time with a void* array and not with a heterogeneous data struct.

Attempt to solve problem

As I read in this thread, dereferencing a nullptr is not always undefined behavior.

I read CWG-issue #315 also, that states:

> We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5 [expr.ref]. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1 [conv.lval]), which it isn't here.

I wanted to leverage this to get a normal pointer from a pointer-to-member (I don't want to dereference them, I just want to compare pointers-to-members from the same class but with different types).

So I created the following code:

#include <iostream>

class Test
{
    int a;
public:
    static constexpr inline int Test::*memPtr = &Test::a;
    static constexpr inline int* intPtr = &(static_cast<Test*>(nullptr)->*Test::memPtr);
};
    
int main () {
    std::cout << Test::intPtr << std::endl;
}

In my opinion the &(static_cast<Test*>(nullptr)->*Test::memPtr); expression uses the same approach as the code that was discussed in CWG-issue #315.

The code above compiles with MSVC but not with clang or gcc.

I checked if similar code that was mentioned in #315 compiles or not:

struct Test {
  static constexpr int testFun () { return 10; } 
};

int main ()
{
  static constexpr int res{static_cast<Test*>(nullptr)->testFun()};
  static_assert(res == 10, "error");
}

And yes, it does. test code

Should the construct I used in the first example be available in constexpr expressions (as undefined behavior is not allowed there)?


Fun fact: If I modify my original code and add a virtual destructor to the class then both MSVC and clang are happy with it, and gcc crashes. I mean literally, it segfaults.

Fun fact 2: If I remove the virtual destructor and make the class templated gcc and MSVC compiles it, but now clang complains.

答案1

得分: 3

从标准文档关于->*的行为中:

表达式 E1->*E2 被转换为等效形式 (*(E1)).*E2

以及关于.*的说明

将 pm-expression.*cast-expression 缩写为 E1.*E2,其中 E1 被称为对象表达式。如果 E1 的动态类型不包含 E2 所引用的成员,行为是未定义的。

E1 的动态类型(解引用为 nullptr)不存在,因为它是对无对象的引用。因此,此表达式的行为是未定义的。

英文:

From the standard on ->*'s behavior:

> The expression E1->*E2 is converted into the equivalent form (*(E1)).*E2.

And for .*:

> Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.

The dynamic type of E1 (which dereferences a nullptr) does not exist, because it's a reference to no object. Therefore, the behavior of this expression is undefined.

huangapple
  • 本文由 发表于 2023年2月6日 18:51:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/75360335.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定