c++ 使用空指针访问静态成员

标签 c++ c++11 language-lawyer static-members nullptr

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

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

如果未初始化的指针用于访问类和/或结构成员行为是未定义的,但为什么也允许使用空指针访问静态成员。我的程序有什么危害吗?

最佳答案

TL;DR : 你的例子定义明确。仅仅取消引用空指针不会调用 UB。

关于这个话题有很多争论,基本上可以归结为通过空指针进行间接寻址本身是否是 UB。
在您的示例中发生的唯一值得怀疑的事情是对象表达式的评估。特别是,d->a相当于(*d).a根据[expr.ref]/2:

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


*d刚刚被评估:

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



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

在此声明中,*d是根据 [stmt.expr] 的丢弃值表达式。所以*d被单独评估1,就像在 d->a 中一样.
因此,如果 *d;是有效的,或者换句话说,表达式 *d 的评估,你的例子也是。

通过空指针进行间接寻址是否会固有地导致未定义的行为?

有 Unresolved CWG 问题 #232 ,创建于十五年前,涉及这个确切的问题。提出了一个非常重要的论点。报告开始于

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] 中的注释 - 虽然仍然存在 - 不是规范性的。规范性段落被删除以避免 promise 。

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

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] 中的引号仍然存在,并且属于多态类类型的左值,在以下示例中就是这种情况:
int main() try {

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

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

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

该程序的行为是明确定义的(将抛出并捕获异常),并且 表达式 *((A*)0)被评估 因为它不是未评估操作数的一部分。现在如果通过空指针间接诱导UB,那么表达式写为
*((A*)0);

会这样做,诱导 UB,与 typeid 相比,这似乎是荒谬的。设想。 如果上述表达式仅被评估为每个丢弃值表达式为 1,那么在第二个片段 UB 中进行评估的关键区别在哪里? 没有分析 typeid 的现有实现。 -操作数,找到最里面的相应解引用,并用检查包围它的操作数——也会有性能损失。

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

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.



IE。委员会同意这一点。尽管该报告的提议决议,引入了所谓的“空左值”,但从未被采纳……

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 时不太令人信服。

CWG 问题 #315似乎也涵盖了您的情况:

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 (October 2003):

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.



根据这个原理,如果没有进一步的左值到右值转换(=访问存储的值)、引用绑定(bind)、值计算等,通过空指针本身的间接调用不会调用 UB。 (注意事项:使用空指针调用非静态成员函数应该调用 UB,尽管 [class.mfct.non-static]/2 只是模糊地不允许这样做。在这方面,基本原理已经过时了。)

IE。对 *d 的评估不足以调用 UB。不需要对象的身份,也不需要其先前存储的值。另一方面,例如
*p = 123;

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

In all cases, the assignment is sequenced after the value computation of the right and left operands



因为左操作数应该是一个glvalue,所以该glvalue引用的对象的身份必须由[intro.execution]/12中表达式的求值定义中提到的那样确定,这是不可能的(因此导致到 UB)。

1 [expr]/11:

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++ 使用空指针访问静态成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28482809/

相关文章:

C++ 模板类型规范

c++ - 关闭显示器?

c++ - 编译器如何知道是否调用 const 重载?

C++ 11 - 右值引用变量

c++ - Lambda 变量捕获

c++ - 如果存在非捕获局部变量,为什么不能在 Lambda 中使用非限定成员变量?

c - 是否有众所周知的 C 标准 "profiles"?

c++ - 在 C++ 中打印多维 map

C++跨系统标准化数据大小

c++ - 一元和二元否定