什么时候在空实例上调用成员函数会导致未定义的行为? [英] When does invoking a member function on a null instance result in undefined behavior?

查看:39
本文介绍了什么时候在空实例上调用成员函数会导致未定义的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码:

#include <iostream>

struct foo
{
    // (a):
    void bar() { std::cout << "gman was here" << std::endl; }

    // (b):
    void baz() { x = 5; }

    int x;
};

int main()
{
    foo* f = 0;

    f->bar(); // (a)
    f->baz(); // (b)
}

我们预计(b)会崩溃,因为空指针没有对应的成员x.在实践中,(a) 不会崩溃,因为 this 指针从未被使用过.

We expect (b) to crash, because there is no corresponding member x for the null pointer. In practice, (a) doesn't crash because the this pointer is never used.

因为 (b) 取消引用了 this 指针 ((*this).x = 5;) 和 this 为 null,程序进入未定义行为,因为取消引用 null 总是被认为是未定义行为.

Because (b) dereferences the this pointer ((*this).x = 5;), and this is null, the program enters undefined behavior, as dereferencing null is always said to be undefined behavior.

(a) 会导致未定义的行为吗?如果两个函数(和 x)都是静态的呢?

Does (a) result in undefined behavior? What about if both functions (and x) are static?

推荐答案

(a)(b) 都会导致未定义的行为.通过空指针调用成员函数总是未定义的行为.如果函数是静态的,那么它在技术上也是未定义的,但存在一些争议.

Both (a) and (b) result in undefined behavior. It's always undefined behavior to call a member function through a null pointer. If the function is static, it's technically undefined as well, but there's some dispute.

首先要理解的是为什么取消引用空指针是未定义的行为.在 C++03 中,这里实际上有点歧义.

The first thing to understand is why it's undefined behavior to dereference a null pointer. In C++03, there's actually a bit of ambiguity here.

尽管 取消引用空指针会导致未定义的行为" 在 §1.9/4 和 §8.3.2/4 的注释中都有提及,但从未明确说明.(注释是非规范的.)

Although "dereferencing a null pointer results in undefined behavior" is mentioned in notes in both §1.9/4 and §8.3.2/4, it's never explicitly stated. (Notes are non-normative.)

但是,可以尝试从 §3.10/2 中推导出来:

However, one can try to deduced it from §3.10/2:

左值指的是一个对象或函数.

An lvalue refers to an object or function.

解引用时,结果是一个左值.空指针指向一个对象,因此当我们使用左值时,我们有未定义的行为.问题是上一句从来没有说过,那么使用"左值是什么意思?甚至完全生成它,或者在执行左值到右值转换的更正式意义上使用它?

When dereferencing, the result is an lvalue. A null pointer does not refer to an object, therefore when we use the lvalue we have undefined behavior. The problem is that the previous sentence is never stated, so what does it mean to "use" the lvalue? Just even generate it at all, or to use it in the more formal sense of perform lvalue-to-rvalue conversion?

无论如何,它绝对不能转换为右值(第 4.1/1 节):

Regardless, it definitely cannot be converted to an rvalue (§4.1/1):

如果左值所指的对象不是 T 类型的对象,也不是从 T 派生的类型的对象,或者如果该对象未初始化,则需要进行此转换的程序具有未定义的行为.

If the object to which the lvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior.

这里绝对是未定义的行为.

Here it's definitely undefined behavior.

歧义来自于尊重但不使用来自无效指针的值是否是未定义的行为(即,获取左值但不将其转换为右值).如果不是,则 int *i = 0;*一世;&(*i); 是明确定义的.这是一个活跃问题.

The ambiguity comes from whether or not it's undefined behavior to deference but not use the value from an invalid pointer (that is, get an lvalue but not convert it to an rvalue). If not, then int *i = 0; *i; &(*i); is well-defined. This is an active issue.

所以我们有一个严格的取消引用一个空指针,得到未定义的行为"视图和一个弱的使用一个取消引用的空指针,得到未定义的行为"视图.

So we have a strict "dereference a null pointer, get undefined behavior" view and a weak "use a dereferenced null pointer, get undefined behavior" view.

现在我们考虑这个问题.

Now we consider the question.

是的,(a) 导致未定义的行为.实际上,如果 this 为 null,则 无论函数的内容如何,结果都是未定义的.

Yes, (a) results in undefined behavior. In fact, if this is null then regardless of the contents of the function the result is undefined.

这来自§5.2.5/3:

This follows from §5.2.5/3:

如果 E1 的类型为指向 X 类的指针",则表达式 E1->E2 将转换为等效形式 (*(E1)).E2;

If E1 has the type "pointer to class X," then the expression E1->E2 is converted to the equivalent form (*(E1)).E2;

*(E1) 将导致具有严格解释的未定义行为,并且 .E2 将其转换为右值,使其成为弱解释的未定义行为.

*(E1) will result in undefined behavior with a strict interpretation, and .E2 converts it to an rvalue, making it undefined behavior for the weak interpretation.

这也是直接来自 (§9.3.1/1) 的未定义行为:

It also follows that it's undefined behavior directly from (§9.3.1/1):

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

If a nonstatic 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.

<小时>

对于静态函数,严格解释和弱解释会有所不同.严格来说,它是未定义的:


With static functions, the strict versus weak interpretation makes the difference. Strictly speaking, it is undefined:

可以使用类成员访问语法来引用静态成员,在这种情况下,将评估对象表达式.

A static member may be referred to using the class member access syntax, in which case the object-expression is evaluated.

也就是说,它就像是非静态的一样被评估,我们再次使用 (*(E1)).E2 取消引用一个空指针.

That is, it's evaluated just as if it were non-static and we once again dereference a null pointer with (*(E1)).E2.

然而,因为 E1 不用于静态成员函数调用,如果我们使用弱解释,调用是明确定义的.*(E1) 产生一个左值,静态函数被解析,*(E1) 被丢弃,函数被调用.没有左值到右值的转换,因此没有未定义的行为.

However, because E1 is not used in a static member-function call, if we use the weak interpretation the call is well-defined. *(E1) results in an lvalue, the static function is resolved, *(E1) is discarded, and the function is called. There is no lvalue-to-rvalue conversion, so there's no undefined behavior.

在 C++0x 中,从 n3126 开始,歧义仍然存在.现在,请注意安全:使用严格的解释.

In C++0x, as of n3126, the ambiguity remains. For now, be safe: use the strict interpretation.

这篇关于什么时候在空实例上调用成员函数会导致未定义的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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