c ++使用空指针访问静态成员 [英] c++ access static members using null pointer

查看:141
本文介绍了c ++使用空指针访问静态成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近尝试下面的程序,它编译,运行良好,产生预期的输出,而不是任何运行时错误。

Recently tried the following program and it compiles, runs fine and produces expected output instead of any runtime error.

#include <iostream>
class demo
{
    public:
        static void fun()
        {
            std::cout<<"fun() is called\n";
        }
        static int a;
};
int demo::a=9;
int main()
{
    demo* d=nullptr;
    d->fun();
    std::cout<<d->a;
    return 0;
}

如果使用未初始化的指针来访问类和/未定义,但为什么允许使用空指针访问静态成员。

If an uninitialized pointer is used to access class and/or struct members behaviour is undefined, but why it is allowed to access static members using null pointers also. Is there any harm in my program?

推荐答案

TL; DR :您的示例已明确定义。只是解除引用一个空指针不是调用UB。

TL;DR: Your example is well-defined. Merely dereferencing a null pointer is not invoking UB.

这个话题有很多争论,基本上归结于通过空指针的间接是否为UB。

在你的例子中发生的唯一可疑的事情是对对象表达式的求值。特别地,根据[expr.ref], d-> a 等价于(* d).a / 2:

There is a lot of debate over this topic, which basically boils down to whether indirection through a null pointer is itself UB.
The only questionable thing that happens in your example is the evaluation of the object expression. In particular, d->a is equivalent to (*d).a according to [expr.ref]/2:


表达式 E1-> E2 形式
(*(E1))E2 ; 5.2.5的其余部分将只处理第一个
选项(点)。

The expression E1->E2 is converted to the equivalent form (*(E1)).E2; the remainder of 5.2.5 will address only the first option (dot).


评估点或箭头前的后缀表达式; 65 该评估的
结果与 id-expression 一起确定
整个后缀表达式的结果。

The postfix expression before the dot or arrow is evaluated;65 the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.

65)如果对类成员访问表达式求值,即使结果不必要
,也会发生子表达式求值,以确定整个后缀表达式的值,例如,如果 id-expression 表示一个静态成员。

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

让我们提取代码的关键部分。考虑表达式语句

Let's extract the critical part of the code. Consider the expression statement

*d;

在此语句中, * d 是根据[stmt.expr]丢弃值表达式。所以 * d 只是在<$> 1 中求值,与 d-> a

因此,如果 * d; 有效,或换句话说,表达式 * d

In this statement, *d is a discarded value expression according to [stmt.expr]. So *d is solely evaluated1, just as in d->a.
Hence if *d; is valid, or in other words the evaluation of the expression *d, so is your example.

是开放CWG问题 #232 ,创建超过十五年前,这涉及这个确切的问题。提出了一个非常重要的论点。该报告以

There is the open CWG issue #232, created over fifteen years ago, which concerns this exact question. A very important argument is raised. The report starts with


在IS状态中,至少有两个地方通过
空指针间接产生未定义的行为: 1.9 [intro.execution]
第4段给出了解除引用空指针作为
未定义行为的示例,8.3.2 [dcl.ref]段落4(在注释中)使用
这个假定的未定义的行为作为
不存在空引用的理由。

At least a couple of places in the IS state that indirection through a null pointer produces undefined behavior: 1.9 [intro.execution] paragraph 4 gives "dereferencing the null pointer" as an example of undefined behavior, and 8.3.2 [dcl.ref] paragraph 4 (in a note) uses this supposedly undefined behavior as justification for the nonexistence of "null references."

请注意,以覆盖 const 对象的修改,并且[dcl.ref]中的注释 - 尽管仍然存在 - 不是规范性的。规范性段落被删除,以避免承诺。

Note that the example mentioned was changed to cover modifications of const objects instead, and the note in [dcl.ref] - while still existing - is not normative. The normative passage was removed to avoid commitment.


但是,5.3.1 [expr.unary.op]第1段描述了一元
* 运算符,不会说如果
操作数是一个空指针的行为是未定义的,可以预期。此外,至少
一个段落给解引用一个空指针良好定义的行为:
5.2.8 [expr.typeid]段落2说

However, 5.3.1 [expr.unary.op] paragraph 1, which describes the unary "*" operator, does not say that the behavior is undefined if the operand is a null pointer, as one might expect. Furthermore, at least one passage gives dereferencing a null pointer well-defined behavior: 5.2.8 [expr.typeid] paragraph 2 says


如果通过将一元*运算符
应用于指针并且指针是空指针值(4.10
[conv.ptr])获得左值表达式,则typeid表达式抛出bad_typeid异常
(18.7.3 [bad.typeid])。

If the lvalue expression is obtained by applying the unary * operator to a pointer and the pointer is a null pointer value (4.10 [conv.ptr]), the typeid expression throws the bad_typeid exception (18.7.3 [bad.typeid]).

这不一致,应该清除。

最后一点特别重要。 [expr.typeid]中的引号仍然存在,并且适用于多态类类型的glvalue,在以下示例中是这种情况:

The last point is especially important. The quote in [expr.typeid] still exists and appertains to glvalues of polymorphic class type, which is the case in the following example:

int main() try {

    // Polymorphic type
    class A
    {
        virtual ~A(){}
    };

    typeid( *((A*)0) );

}
catch (std::bad_typeid)
{
    std::cerr << "bad_exception\n";
}

这个程序的行为是明确定义的catched)和 *((A *)0)的表达式,因为它不是未求值的操作数的一部分。现在如果通过空指针的间接引发UB,那么表达式写成

The behavior of this program is well-defined (an exception will be thrown and catched), and the expression *((A*)0) is evaluated as it isn't part of an unevaluated operand. Now if indirection through null pointers induced UB, then the expression written as

*((A*)0);

就是这样,诱发UB,与 typeid 场景。 如果上面的表达式只是在每个废值表达式都为 1 时评估,那么在第二个片段UB中评估的关键区别是什么?没有现有的实现分析 typeid - 操作数,找到最内层,相应的取消引用,并用检查包围其操作数 - 也会造成性能损失。

would be doing just that, inducing UB, which seems nonsensical when compared to the typeid scenario. If the above expression is merely evaluated as every discarded-value expression is1, where is the crucial difference that makes the evaluation in the second snippet UB? There is no existing implementation that analyzes the typeid-operand, finds the innermost, corresponding dereference and surrounds its operand with a check - there would be a performance loss, too.

该问题中的注释随后结束了简短的讨论:

A note in that issue then ends the short discussion with:


我们同意标准中的方法似乎还好: p = 0; * p;
本身不是一个错误。
一个左值到右值的转换将给予
它未定义的行为。

We agreed that the approach in the standard seems okay: p = 0; *p; is not inherently an error. An lvalue-to-rvalue conversion would give it undefined behavior.

委员会就此达成一致。虽然本报告中提出的引入所谓空白价值的决议从未获得通过...

I.e. the committee agreed upon this. Although the proposed resolution of this report, which introduced so-called "empty lvalues", was never adopted…


然而,不可修改是编译时概念,而事实上
这涉及运行时值,因此应该产生未定义的
行为。此外,还有其他上下文,其中lvalue可以
出现,例如左操作数。或。*,也应该是
限制。需要额外起草。

However, "not modifiable" is a compile-time concept, while in fact this deals with runtime values and thus should produce undefined behavior instead. Also, there are other contexts in which lvalues can occur, such as the left operand of . or .*, which should also be restricted. Additional drafting is required.

... 不会影响合理性。再次,应该注意,这个问题甚至在C ++ 03之前,这使得它在我们接近C ++ 17时更不令人信服。

that does not affect the rationale. Then again, it should be noted that this issue even precedes C++03, which makes it less convincing while we approach C++17.

CWG问题 # 315 似乎也适用于您的情况:

CWG-issue #315 seems to cover your case as well:


另一个要考虑的实例是调用成员函数
从空指针:

Another instance to consider is that of invoking a member function from a null pointer:

  struct A { void f () { } };
  int main ()
  {
    A* ap = 0;
    ap->f ();
  }

[...]

Rationale(2003年10月):

Rationale (October 2003):

我们同意应该允许这个例子。 p-> f()被重写为
(* p).f 5.2.5 [expr.ref]。 * p
p 为空时不是错误,除非将lvalue转换为右值

We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5 [expr.ref]. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1 [conv.lval]), which it isn't here.



According to this rationale, indirection through a null pointer per se does not invoke UB without further lvalue-to-rvalue conversions (=accesses to stored value), reference bindings, value computations or the like. (Nota bene: Calling a non-static member function with a null pointer should invoke UB, albeit merely hazily disallowed by [class.mfct.non-static]/2. The rationale is outdated in this respect.)

仅仅评估 * d 不足以调用UB。对象的身份不是必需的,它也不是其先前存储的值。另一方面,例如

I.e. a mere evaluation of *d does not suffice to invoke UB. The identity of the object is not required, and neither is its previously stored value. On the other hand, e.g.

*p = 123;

未定义,因为有左操作数的计算值,[expr.ass] / 1:

is undefined since there is a value computation of the left operand, [expr.ass]/1:


在所有情况下,赋值在右和左操作数的值计算

由于左操作数应该是一个glvalue,所以该glval引用的对象的标识必须如上所述确定通过在[intro.execution] / 12中的表达式的定义,这是不可能的(并且因此导致UB)。

Because the left operand is expected to be a glvalue, the identity of the object referred to by that glvalue must be determined as mentioned by the definition of evaluation of an expression in [intro.execution]/12, which is impossible (and thus leads to UB).

1 [expr] / 11:

1 [expr]/11:


在某些上下文中,表达式只出现其副作用。
这样的表达式称为舍弃值表达式
表达式被计算,其值被舍弃。
[...]当且仅当表达式是
volatile限定类型的glvalue并且[...]

In some contexts, an expression only appears for its side effects. Such an expression is called a discarded-value expression. The expression is evaluated and its value is discarded. […]. The lvalue-to-rvalue conversion (4.1) is applied if and only if the expression is a glvalue of volatile-qualified type and […]

这篇关于c ++使用空指针访问静态成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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