2

当指针悬空时,这里出现了一个问题,询问“为什么这有效”。答案是它是 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 吗?

4

3 回答 3

9

你正在推理实践中发生的事情。允许未定义的行为做您期望的事情......但不能保证。

对于非静态情况,使用以下规则可以直接证明[class.mfct.non-static]

如果为不属于 类型或派生自 的X类型的对象调用类的非静态成员函数,则行为未定义。XX

请注意,没有考虑非静态成员函数是否访问*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()来消除if它后面的测试。

即使你认为你知道你的平台做什么,也不要做 UB 的事情。 优化不稳定性是真实存在的。


此外,该标准的限制比您想象的还要严格。这也会导致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),试图创建对不存在对象的引用。有几个建议允许这样做,并且只禁止允许在这样的引用上进行左值->右值转换,但到目前为止这些都被拒绝了;迄今为止,在标准的所有版本中,即使形成参考也是非法的。

于 2018-03-10T20:02:59.473 回答
1

编译器没有义务通过传递一个指向类实例的指针来实现成员函数。是的,有伪指针“this”,但它是不相关的元素,保证被“理解”。

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

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

如果成员函数(方法)表现得像那样并且意味着要像那样调用它应该是静态成员函数(方法)。静态方法不访问非静态字段,也不调用类的非静态方法。如果它是静态的,调用也可能如下所示:

Foo::function(); 
于 2018-03-10T20:14:04.783 回答
1

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

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

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

于 2018-03-10T20:02:41.783 回答