通过无效指针访问静态成员:保证“工作”?

时间:2011-03-09 16:29:56

标签: c++

设置

鉴于此用户定义的类型:

struct T
{
    static int x;
    int y;

    T() : y(38);
};

并将必要的定义放在某处有用:

int T::x = 42;

以下是将int的值流式传输到 stdout 的规范方法:

std::cout << T::x;

控制

同时,由于T的实例不存在,以下(当然)无效:

T* ptr = NULL; // same if left uninitialised
std::cout << ptr->y;

问题

现在考虑以下代码中可怕的恶与恶:

T* ptr = NULL;
std::cout << ptr->x; // remember, x is static

取消引用ptr无效,如上所述。即使这里没有物理内存解除引用,我相信它仍然算作一个,使上面的代码为UB。或者......是吗?

14882:2003 5.2.5 / 3明确指出a->b已转换为(*(a)).b,并且:

  

评估点或箭头之前的后缀表达式;   即使结果不必确定整个后缀表达式的值,也会发生此评估,例如,如果id-expression表示静态成员。

但目前尚不清楚“评估”是否涉及实际的解除引用。事实上,在处理静态成员时,14882:2003或n3035似乎都没有明确地说明指针表达式是否必须求值为指向有效实例的指针。

我的问题是,这有多么无效?它是否真的被标准特别禁止(即使没有物理解除引用),或者它只是我们可以逃脱的语言的怪癖?即使它被禁止,我们还期望GCC / MSVC / Clang在多大程度上安全地对待它?

我的g ++ 4.4似乎产生的代码永远不会尝试将[invalid] this指针推入堆栈,并关闭优化。

BTW 如果T是多态的,那么这不会影响这一点,因为静态成员不能是虚拟的。

3 个答案:

答案 0 :(得分:10)

  

目前尚不清楚这里的“评估”是否涉及实际的解除引用。

我在这里读“评估”为“评估子表达式”。这意味着会对一元*进行求值,并通过空指针执行间接操作,从而产生未定义的行为。

这个问题(通过空指针访问静态成员)在另一个问题中讨论,When does invoking a member function on a null instance result in undefined behavior?虽然它具体讨论了成员函数,但我认为数据成员在这方面没有任何不同的原因。对那里的问题有一些很好的讨论。

针对C ++标准报告了一个缺陷,询问“是否通过空指针调用静态成员函数未定义?” (参见CWG Defect 315)此缺陷已关闭,其解决方案表明通过空指针调用静态成员函数是有效的:

  根据5.2.5 [expr.ref],

p->f()被重写为(*p).f()。除非将左值转换为右值,否则*p为空时p不是错误

但是,这个决议实际上是错误的。

它预先假定了一个“空左值”的概念,它是另一个缺陷CWG defect 232的建议解决方案的一部分,它提出了更一般的问题,“间接通过空指针未定义的行为吗?”

对该缺陷的解析会使某些形式的间接通过空指针(如调用静态成员函数)有效。但是,该缺陷仍然存在,其解决方案尚未被C ++标准采用。在关闭该缺陷并将其解决方案合并到C ++标准之前,通过空指针进行间接寻址(或取消引用空指针,如果更喜欢该术语)总是会产生未定义的行为。

答案 1 :(得分:3)

关于p-&gt; a,其中p是空指针,以及静态数据成员: §9.4/ 2说“可以使用类成员引用静态成员 访问语法,在这种情况下,对object-expression进行求值。“( “object-expression”是左边的表达式。或 - &gt;。)

答案 2 :(得分:0)

从计算机端看到OOP,这是另一种计算数据驻留在内存中的方法。当数据不是静态时 - 它是从实例指针计算的,当数据是静态时,它总是被计算为数据段中的固定指针。模板没有任何补充,因为在编译时解决了。

因此,使用NULL作为起始指针是一种相当流行的技术(例如,为了持久目的而评估类中的字段偏移量)

因此上面的代码对于静态数据是正确的。