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

C++ c++;使用空指针访问静态成员,c++,c++11,language-lawyer,static-members,nullptr,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

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

#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;
}
#包括
课堂演示
{
公众:
静态void fun()
{

STD::CUT

这里你看到的是我认为在C++语言和其他属于同一个通用编程语言家族的语言中的一个构思不好和不幸的设计选择。 这些语言允许您使用对类实例的引用来引用类的静态成员。实例引用的实际值当然会被忽略,因为不需要任何实例来访问静态成员

因此,在
d->fun();
中,编译器仅在编译期间使用
d
指针来确定您引用的是
demo
类的一个成员,然后它将忽略它。编译器不会发出任何代码来取消对指针的引用,因此在运行时指针将为NULL这一事实并不重要

因此,您看到的情况完全符合语言的规范,我认为规范在这方面受到影响,因为它允许发生不合逻辑的事情:使用实例引用引用静态成员


大多数语言中的大多数编译器实际上都能发出警告,因为我不知道你的编译器,但是你可能想检查一下,因为你没有收到警告,因为你做了什么,可能意味着你没有足够的警告。C++版草案N337:< /P> < /P> < P> 9.4个静态成员

2类
X
static
成员可以使用限定id表达式
X::s
引用;无需使用类成员访问语法(5.2.5)来引用
static
成员。可以引用
static
成员 要使用类成员访问语法,将对对象表达式求值

在关于对象表达式的部分

5.2.5类成员访问权限

4如果E2被声明为具有类型“reference to T”,那么E1.E2是一个左值;E1.E2的类型是T。否则, 以下规则之一适用

-如果
E2
是一个
static
数据成员,并且
E2
的类型是
T
,则
E1.E2
是一个左值;表达式指定类的命名成员。
E1.E2
的类型是T

根据标准的最后一段,表达式为:

  d->fun();
  std::cout << d->a;
d->fun();
std::cout a;

之所以有效,是因为它们都指定类的命名成员,而不考虑
d

TL;DR的值:您的示例定义得很好。仅取消对空指针的引用不会调用UB

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

表达式
E1->E2
转换为等效形式
(*(E1)).E2
;5.2.5的其余部分将仅解决第一个问题 选项(点)

*d
刚刚进行了评估:

计算点或箭头之前的后缀表达式;65 该求值的结果与id表达式一起确定 整个后缀表达式的结果

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

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

*d;
在此语句中,
*d
是根据[stmt.expr]丢弃的值表达式。因此,
*d
是单独计算的1,就像
d->a

因此,如果
*d;
是有效的,或者换句话说,表达式
*d
的计算是有效的,那么您的示例也是有效的

通过空指针的间接寻址是否固有地导致未定义的行为? 有一个开放的CWG问题,创建于15年前,涉及到这个确切的问题。提出了一个非常重要的论点。报告从

该州至少有几个地方是通过 null指针产生未定义的行为:1.9[简介执行] 第4段给出了“取消对空指针的引用”作为示例 未定义的行为,8.3.2[dcl.ref]第4段(注释中)使用 这被认为是未定义的行为,作为 不存在“空引用”

请注意,所提到的示例已更改为包含对
const
对象的修改,并且[dcl.ref]中的注释(尽管仍然存在)不规范。删除了规范性段落以避免承诺

然而,5.3.1[expr.unary.op]第1段描述了一元数 “
*
”运算符,如果 正如人们所期望的,操作数是空指针 其中一段给出了一个定义良好的空指针解引用行为: 5.2.8[解释类型ID]第2段

如果通过应用一元*运算符获得左值表达式 指向指针,指针为空指针值(4.10 [conv.ptr]),typeid表达式抛出错误的\u typeid异常 (18.7.3[错误的类型ID])

这是不一致的,应该清理

最后一点特别重要,[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);
  struct A { void f () { } };
  int main ()
  {
    A* ap = 0;
    ap->f ();
  }
*p = 123;