英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论