C++ 中的这种未定义行为是不是从悬空指针调用函数
Posted
技术标签:
【中文标题】C++ 中的这种未定义行为是不是从悬空指针调用函数【英文标题】:Is this undefined behaviour in C++ calling a function from a dangling pointerC++ 中的这种未定义行为是否从悬空指针调用函数 【发布时间】:2018-03-10 19:59:00 【问题描述】:当指针悬空时,这里出现了一个问题,询问“为什么这有效”。答案是它是 UB,这意味着它可能有效,也可能无效。
我在一个教程中了解到:
#include <iostream>
struct Foo
int member;
void function() std::cout << "hello";
;
int main()
Foo* fooObj = nullptr;
fooObj->member = 5; // This will cause a read access violation but...
fooObj->function(); // Because this doesn't refer to any memory specific to
// the Foo object, and doesn't touch any of its members
// It will work.
这是否相当于:
static void function(Foo* fooObj) // Foo* essentially being the "this" pointer
std::cout << "Hello";
// Foo pointer, even though dangling or null, isn't touched. And so should
// run fine.
我错了吗?即使正如我解释的那样只是调用一个函数而不访问无效的 Foo 指针,它是 UB 吗?
【问题讨论】:
这是一个充满争议的话题。可能重复的示例:***.com/a/28483256/560648***.com/q/3498444/560648***.com/q/5248877/560648 这些问题主要集中在访问静态成员上,但访问 no 成员最终是同一个问题 @Lightness Races in Orbit 那么我是否应该假设没有人知道真正的答案但我不应该玩火? 没有真正的答案,它是未定义的,您不可能尝试将特定行为与未定义行为联系起来。 @Zebra:我个人认为你可以放心地认为这是 UB,但这将是一个合理的后备位置是的 @SombreroChicken:它是否有UB(表面上)并不完全清楚;这就是重点 【参考方案1】:您正在推理实践中发生的事情。允许未定义的行为做你期望的事情......但不能保证。
对于非静态情况,使用[class.mfct.non-static]
中的规则很容易证明:
如果为非
X
类型或从X
派生的类型的对象调用类X
的非静态成员函数,则行为未定义。强>
注意这里没有考虑非静态成员函数是否访问*this
。该对象只需要具有正确的动态类型,*(Foo*)nullptr
肯定没有。
特别是,即使在使用您描述的实现的平台上,调用
fooObj->func();
转换成
__assume(fooObj); Foo_func(fooObj);
并且优化不稳定。
这是一个与您的期望相反的示例:
int main()
Foo* fooObj = nullptr;
fooObj->func();
if (fooObj)
fooObj->member = 5; // This will cause a read access violation!
在实际系统上,这可能会导致注释行出现访问冲突,因为编译器使用了 fooObj
不能 在 fooObj->func()
中为 null 的事实来消除紧随其后的if
测试。
即使您认为自己知道自己的平台在做什么,也不要做 UB 的事情。 Optimization instability is real.
此外,标准的限制比您想象的还要严格。这也会导致 UB:
struct Foo
int member;
void func() std::cout << "hello";
static void s_func() std::cout << "greetings";
;
int main()
Foo* fooObj = nullptr;
fooObj->s_func(); // well-formed call to static member,
// but unlike Foo::s_func(), it requires *fooObj to be a valid object of type Foo
标准的相关部分在[expr.ref]
:
表达式
E1->E2
被转换为等价形式(*(E1)).E2
以及随附的脚注
如果类成员访问表达式被求值,子表达式求值即使结果对于确定整个后缀表达式的值是不必要的,例如如果 id-expression 表示静态成员.
这意味着有问题的代码肯定会评估(*fooObj)
,试图创建对不存在对象的引用。有几个建议允许并且只禁止允许在这样的引用上进行左值->右值转换,但到目前为止这些都被拒绝了;迄今为止,在标准的所有版本中,即使形成引用也是非法的。
【讨论】:
你能举出万能的标准吗? @JiveDadson:完成。 在 Lightness 提供的最高投票答案的第一个问题链接中说“TL;DR:仅仅取消引用空指针不会调用 UB。关于这个话题有很多争论,基本上可以归结为通过空指针间接是否本身是UB。”我很困惑。也许我不应该问这个问题,生活会变得更简单。 @Zebrafish:在该答案的 cmets 中,您发现 Columbo 的答案不是基于语言规则,而是基于提案(被拒绝) 嗯,真的很神秘和深奥,但谢谢。我会认为它是UB。这很有趣,因为我是从一个受人尊敬的教程中第一次看到这个的,我猜任何人都可能是错的。【参考方案2】:实际上,这通常是主要编译器实现成员函数的方式,是的。这意味着您的测试程序可能看起来“运行良好”。
话虽如此,取消引用指向nullptr
的指针是未定义的行为,这意味着所有赌注都已关闭,整个程序及其输出毫无意义,任何事情都可能发生。
您永远不能依赖这种行为,特别是优化器可能会弄乱所有这些代码,因为他们被允许假设 fooObj
永远不是 nullptr
。
【讨论】:
【参考方案3】:编译器没有义务通过传递一个指向类实例的指针来实现成员函数。是的,有伪指针“this”,但它是不相关的元素,保证被“理解”。
nullptr
指针不指向任何现有对象,并且 -> () 调用该对象的成员。从标准的角度来看,这是无稽之谈,这种操作的结果是不确定的(并且可能是灾难性的)。
如果function()
是虚拟的,则允许调用失败,因为函数的地址将不可用(vtable 可能作为对象的一部分实现,如果对象不存在则不存在)。
如果成员函数(方法)具有这样的行为并且意味着要像那样调用它,则它应该是静态成员函数(方法)。静态方法不访问非静态字段,也不调用类的非静态方法。如果它是静态的,调用也可能如下所示:
Foo::function();
【讨论】:
对静态成员的访问可能看起来像这样,但在 C++ 中,与其他一些语言不同,.
和 ->
运算符也可以与静态成员一起使用(有时在模板代码中很方便,当您不知道事物是否是静态的,或者类型难以命名但您有一个方便的实例时)
@BenVoigt 是的,“可以”更适合这里,静态成员仍然是成员以上是关于C++ 中的这种未定义行为是不是从悬空指针调用函数的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 中删除空指针是不是被认为是未定义的行为? [复制]