【问题标题】:Accessing static member through invalid pointer: guaranteed to "work"? [duplicate]通过无效指针访问静态成员:保证“工作”? [复制]
【发布时间】:2011-07-12 01:21:21
【问题描述】:

设置

给定这个用户定义的类型:

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-&gt;b 转换为 (*(a)).b,并且:

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

但不清楚这里的“评估”是否涉及实际的取消引用。事实上,无论是 14882:2003 还是 n3035 似乎都没有明确说明在处理静态成员时指针表达式是否必须计算为指向有效实例的指针。

我的问题是,这有多无效?它真的被标准明确禁止(即使没有物理取消引用),还是只是我们可能逃脱的语言的怪癖?即使它被禁止,我们还能在多大程度上期望 GCC/MSVC/Clang 安全地对待它?

我的 g++ 4.4 似乎生成的代码从未尝试将 [invalid] this 指针压入堆栈,优化已关闭。

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

【问题讨论】:

  • When does invoking a member function on a null instance result in undefined behavior? 对此进行了一些相关的讨论(您的问题是关于静态数据成员;静态成员函数在另一个问题中进行了讨论)。共识是它应该是允许的,但不是;有一个封闭的 DR 可以允许,但 DR 依赖于对仍然开放的 DR 的决议(该共识是否正确尚待辩论:-D)
  • 问题是已关闭的 DR(CWG 缺陷 315)说“当p 为空时,*p 不是错误,除非将左值转换为右值。”但是,这依赖于“空左值”的概念,这是对仍然开放的CWG Defect 232 提出的解决方案的一部分。
  • @James:所以我们说这只是 C++ 美味混乱的另一个不确定的怪癖? :)
  • 实际上,是的。我几乎可以肯定,在你的引用中,“评估”的意思是“操作员做的事情”,而* 所做的事情是“执行间接”,所以你最终通过空指针执行间接,这会产生 undefined行为。
  • @James:哦,太高兴了。顺便说一句,我会接受这个答案。

标签: c++


【解决方案1】:

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

【讨论】:

  • 当然,已评估。问题是,评估后的表达式 (0) 是否被显式取消引用?
  • 表达式为 (*ptr).x; -> 运算符是根据 * 定义的。并评估 (*ptr) 取消引用指针。
  • 标准在哪里明确说明了这种情况? (问题已经得到解答:标准没有明确表示。)
  • @Tomalak §5.2.5/2 明确地将 p->x 定义为 (*p).x 的等价物。 §5.3.1/1 说“应用它的表达式 [一元 * 运算符] 应该是指向对象类型的指针,或指向函数类型的指针”。 0 既不指向对象也不指向函数(任何类型),因此 *0 是未定义的行为。
【解决方案2】:

不清楚这里的“评估”是否涉及实际的取消引用。

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

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

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

p-&gt;f() 根据 5.2.5 [expr.ref] 重写为 (*p).f()。当p 为空时,*p 不是错误,除非将左值转换为右值

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

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

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

【讨论】:

  • 这是否可以推广到非 NULL 无效指针,但缺陷解决方案不会为它们解决这个问题?
  • @Tomalak:是的,因为使用无效指针做任何事情都会产生未定义的行为。不过,我必须深入研究标准才能找到具体的引用。
  • 是的,IIRC 甚至打印无效指针的内容(是的,我的意思是指针的内容)是 UB,因为它是“使用”。虽然这不是我的想法。
  • 更新: 在 FDIS 这个appears to still be the case,所以不幸的是,看起来我们在 C++11 中遇到了这个问题。 :(
【解决方案3】:

从计算机端看 OOP 是另一种计算数据在内存中的位置的方法。当数据不是静态的 - 它是从实例指针计算的,当数据是静态的时,它总是作为数据段中的固定指针计算。模板没有添加任何内容,因为在编译时已解决。

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

所以上面的代码对于静态数据是正确的。

【讨论】:

  • 我宁愿对标准的引用有一些基本原理,也不愿知道流行的技术是什么!另外,我不确定我是否关注您关于使用实例指针仅访问没有实例的静态数据的人?我意识到我是发布代码的人,但它很丑陋,不应该在现实生活中使用......
  • @Tomalak Geret'kal 例如,使用 NULL 指针查看我的答案:stackoverflow.com/questions/1400660/…。指针的相对使用我的意思是编译器不需要你的指针,所以它根本不被使用并且在编译时被拒绝。甚至还有警告(例如:使用静态数据指针,忽略)
  • @Dewfy:好的,没关系,但我正在寻找证据证明它是否真的像你声称的那样“安全”。我怀疑不是。顺便说一句,你为什么不在那里使用指向成员的指针?
  • @Tomalak Geret'kal - 抱歉没有及时按回车,所以再次复制:指针的相对用法我的意思是编译器不需要你的指针,所以根本不用它在编译时被拒绝。甚至还有警告(例如:使用静态数据指针,忽略)
  • @Dewfy:好的,但是标准在多大程度上保证了这一点?哪些编译器会明确允许它?这是在哪里记录的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-13
  • 1970-01-01
  • 2020-03-13
  • 2020-10-02
  • 2011-03-30
相关资源
最近更新 更多