这是C ++中的未定义行为从悬空指针调用函数

时间:2018-03-10 19:59:00

标签: c++ pointers undefined-behavior dangling-pointer

这里提出了一个问题,问问"为什么这有效?"当一个指针变得晃来晃去。答案是它的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.
}

我错了吗?是UB,即使我解释只是调用一个函数而不是访问无效的Foo指针?

3 个答案:

答案 0 :(得分:9)

你在推理实践中会发生什么。未定义的行为可以做你期望的事情......但是不能保证。

对于非静态情况,可以直接使用[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 在{ {1}}取消跟随它的fooObj->func()测试。

即使您认为自己了解平台的功能,也不要做UB的事情。 Optimization instability is real.

此外,您可能会认为标准更具限制性。这将导致UB:

if

标准的相关部分位于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

和随附的脚注

  

如果计算了类成员访问表达式,则即使不需要结果来确定整个后缀表达式的值,也会发生子表达式求值,例如,如果 id-expression 表示静态成员

这意味着有问题的代码肯定会评估(*(E1)).E2,尝试创建对不存在的对象的引用。已经提出了几个允许这样做的提议,并且仅禁止在这样的引用上允许左值 - >右值转换,但是这些已经被拒绝了;甚至形成参考文献在迄今为止的所有版本的标准中都是非法的。

答案 1 :(得分:1)

实际上,这通常是主要编译器实现成员函数的方式,是的。这意味着您的测试程序可能看起来运行“很好”。

话虽如此,取消引用指向nullptr的指针是未定义的行为,这意味着所有的赌注都关闭,整个程序及其输出都没有意义,任何事情都可能发生。

你永远不能依赖这种行为,特别是优化器会使所有这些代码混乱,因为他们可以假设fooObj永远不会nullptr

答案 2 :(得分:1)

编译器没有按标准强制实现成员函数,方法是将指针传递给类实例。是的,有伪指针&#34;这个&#34;,但它是不相关的元素,保证&#34;理解&#34;。

nullptr指针不指向任何现有对象,并且 - &gt; ()调用该对象的成员。从标准的角度来看,这是无稽之谈,这种操作的结果是不确定的(并且可能是灾难性的)。

如果function()是虚拟的,那么允许调用失败,因为函数的地址将不可用(vtable可能被实现为对象的一部分,并且如果对象不存在则不存在#t )。

如果成员函数(方法)的行为与此类似,并且意味着要调用它应该是静态成员函数(方法)。静态方法不访问非静态字段,也不调用非静态类方法。如果它是静态的,那么调用也可能如下所示:

Foo::function();