C ++中的这种未定义行为是从悬空指针调用函数吗? [英] Is this undefined behaviour in C++ calling a function from a dangling pointer

查看:147
本文介绍了C ++中的这种未定义行为是从悬空指针调用函数吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当指针悬空时,SO出现了一个问题,问这为什么起作用".答案是它是UB,这意味着它可能行不通.

A question came up here on SO asking "Why is this working" when a pointer became dangling. The answers were that it's UB, which means it may work or not.

我在一个教程中了解到:

I learned in a tutorial that:

#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吗?

Am I wrong about this? Is it UB even though as I explained just calling a function and not accessing the invalid Foo pointer?

推荐答案

您正在思考实际发生的情况.可以执行未定义的行为来完成您期望的事情...但是不能保证.

You're reasoning about what happens in practice. Undefined behavior is allowed to do the thing you expect... but it is not guaranteed.

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

For the non-static case, this is straightforward to prove using the rule found in [class.mfct.non-static]:

如果为非X类型或非X派生类型的对象调用了类X的非静态成员函数,则行为是不确定的.

If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

请注意,不考虑非静态成员函数是否访问*this.仅要求对象具有正确的动态类型,而*(Foo*)nullptr当然不是.

Note that there's no consideration about whether the non-static member function accesses *this. The object is simply required to have the correct dynamic type, and *(Foo*)nullptr certainly does not.

尤其是,即使在使用您描述的实现的平台上,调用

In particular, even on platforms which use the implementation you describe, the call

fooObj->func();

被转换为

__assume(fooObj); Foo_func(fooObj);

并且优化不稳定.

这是一个与您的预期相反的示例:

Here's an example which will work contrary to your expectations:

int main()
{
    Foo* fooObj = nullptr;
    fooObj->func();
    if (fooObj) {
        fooObj->member = 5; // This will cause a read access violation!
    }
}

在实际系统上,这很可能导致注释行出现访问冲突,因为编译器使用fooObj 不能fooObj->func()中为null的事实来消除紧随其后的if测试.

On real systems, this is likely to end up with an access violation on the commented line, because the compiler used the fact that fooObj can't be null in fooObj->func() to eliminate the if test following it.

即使您认为自己知道平台的功能,也不要做UB的事情. 优化不稳定是真实的.

Don't do things that are UB even if you think you know what your platform does. Optimization instability is real.

此外,您可能会认为标准更加严格.这也会导致UB:

Also, the Standard is even more restrictive that you might think. This will also cause 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]中找到:

The relevant portions of the Standard are found in [expr.ref]:

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

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

和随附的脚注

如果评估了类成员访问表达式,则即使不需要确定整个后缀表达式的值,例如如果 id-expression 表示静态成员,也将进行子表达式评估

If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

这意味着所讨论的代码肯定会评估(*fooObj),尝试创建对不存在对象的引用.有一些提议使这种允许成为可能,并且仅禁止在这样的引用上允许进行lvalue-> rvalue转换,但是到目前为止,这些提议都被拒绝了.到目前为止,即使构成参考也属于非法.

This means that the code in question definitely evaluates (*fooObj), attempting to create a reference to a non-existent object. There have been several proposals to make this allowed and only forbid allowing lvalue->rvalue conversion on such a reference, but those have been rejected this far; even forming the reference is illegal in all versions of the Standard to date.

这篇关于C ++中的这种未定义行为是从悬空指针调用函数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆