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

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

问题描述

请考虑以下代码:

  #include< iostream> 

struct foo
{
//(a):
void bar(){std :: cout< gman在这里< std :: endl; }

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

int x;
};

int main()
{
foo * f = 0;

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

我们期望 到崩溃,因为没有对应的成员 x 为空指针。在实践中,(a)不会崩溃,因为 this 指针从未使用。



因为(b)解除引用 this (* this).x = 5; )和这个是null,程序进入未定义的行为,因为取消引用null总是说未定义的行为。



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

解决方案

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






它是未定义的行为来解引用一个空指针。在C ++ 03中,这里实际上有一点模棱两可。



虽然提到了解引用空指针导致未定义的行为在§1.9/ 4和§8.3.2/ 4中的注释,它从未明确说明。



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



< blockquote>

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


解除引用时,结果是一个左值。空指针不指向对象,因此当我们使用左值时,我们有未定义的行为。问题是,前面的句子从来没有说明,所以什么意思是使用左值?只是甚至生成它,或者在更正式的意义上使用它执行左值到右值转换?



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


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


行为。



歧义来自它是否是未定义的行为,以便尊重但不使用来自无效指针的值一个左值,但不会将其转换为右值)。如果不是,则 int * i = 0; *一世; &(* i); 是定义明确的。这是有效问题



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



现在我们考虑这个问题。






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



这来自§5.2.5/ 3:


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


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



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






















$ b




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


可以使用类成员访问语法来引用静态成员,


也就是说,它的计算方式与非静态方法相同,我们再次使用<$然而,因为 E1 (*(E1))。 c>不在静态成员函数调用中使用,如果我们使用弱解释,则调用是明确定义的。 *(E1)导致一个左值,静态函数被解析, *(E1)函数被调用。没有左值到右值的转换,所以没有未定义的行为。



在C ++ 0x中,从n3126开始,模糊性仍然存在。现在,请放心:使用严格的解释。


Consider the following code:

#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)
}

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.

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.

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

解决方案

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.


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.

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.)

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?

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

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.

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.


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

This follows from §5.2.5/3:

If E1 has the type "pointer to class X," then the expression E1->E2 is converted to the equivalent form (*(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.

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

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.

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

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.

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

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

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