【问题标题】:Why is gcc 4.7.0 giving me a segfault on this code while online ideone(gcc 4.5.1) doesnt?为什么 gcc 4.7.0 给我这段代码的段错误,而在线 ideone(gcc 4.5.1) 没有?
【发布时间】:2012-01-19 04:34:59
【问题描述】:

我有以下代码(仅适用于 gcc):

#include <iostream>
#include <cstdlib>
#include <string>
#include <typeinfo>
#include <cxxabi.h>

const std::string demangle (const char* name) {

    int status = -4;
    char* res = abi::__cxa_demangle(name, 0, 0, &status);
    const char* const demangled_name = (status == 0) ? res : name;
    std::string ret_val(demangled_name);
    std::free(res);

    return ret_val;
}

template <typename T>
const std::string getname (T x)
{
    return demangle(typeid(x).name());
}

int main()
{
   std::add_const<int>::type *p = static_cast<const int *>(0); 
   std::cout << getname(*p) << std::endl;
}

在我的本地计算机上(使用 gcc 4.7.0(实验)它崩溃了(使用 gdb 运行会产生段错误)。但是,使用 ideone.com,它会按预期打印“int”。继承人,a link to the example。另外,去掉模板直接调用demangle(typeid(x).name())就解决了,那么模板有什么问题呢?

编辑 我忘了包含解决问题的 type_traits 标头(doh!),但是我仍然想知道发生了什么更好。

【问题讨论】:

  • 嗯,显而易见的答案是您正在取消引用一个空指针,即 UB,因此您观察到的两个结果都是“正确的”。
  • @todda.speot.is:按照标准的规定,它在 C++ 中是未定义的行为。如果您想在特定时间谈论特定硬件上特定平台上的特定 C++ 实现,那很好,但要意识到您不再谈论 C++ 语言
  • @GMan 诚然,在 C++ 标准中涉及 NULL 指针的某些操作存在未定义的行为,所以我收回我的 cmets。然而open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232 似乎认为它在某些情况下定义了行为,并且在阅读了更多之后似乎在这种情况下它是定义行为Objects of class std::bad_typeid are thrown when typeid is called on a dereferenced null pointer。虽然它对于其他操作是未定义的。
  • @todda.speot.is:请注意,问题仍然存在,AFAIK 未纳入新标准。 (This was an area of interest to me.) 反正那篇文章里的那句话其实太丢人了,标准中的相关章节是§5.2.8/2。它非常明确地表示,typeid 表达式中的表达式 可以采用*p*(p) 等形式,而p 可以为空。这里不是这种情况,UB 已经发生了。
  • @GMan 你说得对,*p 不是typeid 的表达式。

标签: c++ gcc c++11 segmentation-fault


【解决方案1】:
std::add_const<int>::type *p = static_cast<const int *>(0); 

p 是一个空指针,取消引用它(即*p)调用会产生未定义的行为(UB)。你很幸运,它给出了段错误。而且由于它实际上是 UB,所有编译器(和所有版本)可能不会给出段错误,因为这就是 UB 的意思,即任何事情都可能发生。

你为什么不试试这个呢:

std::cout << getname(int()) << std::endl;

【讨论】:

  • @todda.speot.is:你读过链接的文章吗? “虽然这在许多编译器中都能正常工作,但根据 C 标准,它具有未定义的行为,因为它涉及空指针的取消引用和违反别名规则的强制转换。如果其中之一,它也往往会产生令人困惑的编译器诊断参数拼写错误。现代编译器通常使用特殊形式定义宏。这是未定义的行为。停止尝试定义它。
  • @todda.speot.is:C++ 中没有“用户空间”之类的东西。 在 C++ 中取消引用空指针是未定义的行为,尽管这与让特定平台提供额外保证并无矛盾。
  • @GMan 我围绕 C99 标准进行了一番探索,虽然我发现了一些接近的东西来将 addressof 的实现定义为定义的行为,但它已经足够明确了。所以你是对的,我应该读过这篇文章。
  • @GMan : 什么演员表违反了别名规则?
  • @ildjarn:todda.speot.is 已链接到 this Wikipedia page,我正在引用它。
【解决方案2】:

这里有一个微妙的问题:这是一个评估问题。

通常,取消引用空指针是未定义的行为;但是有一个极端情况(至少在 gcc 上),在 p 为 null 的情况下调用 typeid(*p) 是一个有效的表达式。

因此,这里的问题在于您的间接级别:

int* p = 0;

getname(*p);       // p is null, *p invokes undefined behavior

typeid(*p).name(); // p is null, but it's probably okay in `typeid`
                   // because it's an unevaluated operand here

对于那些有兴趣深入研究的人,typeid 表达式在 §5.2.8 (C++11) 中有详细描述。

2/typeid 应用于类型为多态类类型(10.3) 的泛左值表达式时,结果引用代表最派生对象类型的std::type_info 对象(1.8) (即动态类型)glvalue 所指的。如果通过将一元* 运算符应用于指针获得glvalue 表达式并且该指针是空指针值(4.10),则typeid 表达式将引发std::bad_typeid 异常(18.7.3)。

好的,如果p 指向多态类,则定义typeid(*p)...在这种情况下它会抛出异常。

3/typeid 应用于除多态类类型的glvalue 以外的表达式时,结果引用代表表达式静态类型的std::type_info 对象。左值到右值 (4.1)、数组到指针 (4.2) 和函数到指针 (4.3) 转换不适用于表达式。如果表达式的类型是类类型,则类应该是完全定义的。表达式是未计算的操作数 (第 5 条)。

有趣的是,没有提到非多态类的空指针。我怀疑 未评估的操作数 是允许这样做的原因,但尚未找到引用。

6/如果在使用 typeid 之前未包含标头 &lt;typeinfo&gt; (18.7.1),则程序格式错误。

似乎不需要诊断。至少在 gcc 上,我认为它会发出警告。

【讨论】:

  • 谢谢,我真的想知道为什么std::bad_typeid 没有被抛出。
猜你喜欢
  • 2021-12-27
  • 2013-08-08
  • 1970-01-01
  • 1970-01-01
  • 2022-12-17
  • 2012-03-25
  • 1970-01-01
  • 2020-11-13
  • 2014-01-16
相关资源
最近更新 更多