【问题标题】:Why can't I cause a seg fault?为什么我不能导致段错误?
【发布时间】:2011-01-03 22:58:29
【问题描述】:

好的,无论出于何种原因,我都无法导致段错误。我想制作一个,以便我可以使用gdb 来查看如何调试一个。我已经尝试了the Wikipedia 文章中的两个示例,但都不起作用。

第一个:

char *s = "Hello World!";
*s = 'H';

第二个例子:

int main(void) 
{
    main();
}

编辑:我使用 Ubutnu 9.10 和 g++ 作为我的编译器。谁能给我看一些保证段错误的代码?

【问题讨论】:

  • 上述代码的行为是未定义的,这意味着它可以为所欲为,包括“似乎工作”,或者引起鼻恶魔。 :-)
  • 尝试 char* = reinterpret_cast(0x1234); p[0] = 'H';
  • 如果你想引起段错误,我可以把我在大学写的一些 C++ 程序发给你:-)
  • @tster:严格来说,没有答案可以保证一个段错误。那将定义未定义的行为。
  • 你知道我有多少次希望遇到你的问题吗? B-)

标签: c++ segmentation-fault


【解决方案1】:

不可能尝试可靠地取消引用指针。
这是因为应用程序处理内存的方式可能因编译器而异,而且在具有不同选项的同一编译器中(调试/发布模式处理方式不同)。

您可以做的是使用信号显式引发段错误:

#include <signal.h>

int main()
{
    raise(SIGSEGV);
}

【讨论】:

  • 声明“不可能尝试可靠地取消引用指针”是不正确的。 - 你可以使用测试和分析你的代码来确定它可靠的段错误。
  • @user1976:显然你不明白未定义行为的含义。
  • @Loki Astari:未定义的行为意味着(非常)最近的编译器(clang 是最严重的违规者)可以随意烦人并删除您的代码。但是,这并不意味着不能依赖生成的代码来表现出明确定义的行为。您只需要依靠 C++ 标准之外的另一种方法来保证这一点。
  • @user1976:当然你可以使用很多东西可靠地做到这一点。但是您不能使用未定义的行为可靠地做到这一点。并且未定义的行为永远不会被很好地定义。由于在指针有效时取消引用有效指针是明确定义的,因此您可以通过这种方式崩溃。此外,取消引用 nullptr 或无效指针是未定义的行为,因此您无法从中获得明确定义的行为。所以我的声明成立。
  • @Loki Astari:我同意这样的说法——你不能编写可以保证段错误的可移植 C++ 代码。但是,您的陈述是-您不能通过解除引用指针可靠地导致段错误-这是不正确的,解除引用指针正是导致段错误的原因,并且非常可靠。
【解决方案2】:

有史以来最短的段错误:

*(int*)0=0;

【讨论】:

  • @smerlin:为微妙的幽默 +1。
【解决方案3】:

如果你尝试连接两个常量......你会得到一个......至少是一种简单的方法......

strcat("a", "b");

=)

【讨论】:

  • 仅当文字分配在只读内存中时。这显然不是他的情况,否则他的字符串操作首先会导致段错误。
  • 其实strcat("seg", "fault");保证会产生段错误。
【解决方案4】:

您所做的这两件事都会在 C++ 中产生“未定义的行为”(嗯,调用 main() 实际上是明确禁止的)。不能保证它们会导致段错误 - 这取决于很多事情,但主要是您运行的平台,您没有指定。

事实上,任何建议的导致段错误的方法可能有效,也可能无效。这是因为段错误几乎总是与 C++ 的未定义行为概念相关联。

【讨论】:

  • 从语言的角度来看这是未定义的行为,但操作系统可能定义了发生分段错误的条件,您可以在 C++ 中强制这些条件。 (例如:int *p = 0; while (true) *p++ = 10;会在某一时刻碰到一个进程不可写的内存段,在linux或macosx下会触发段错误)
【解决方案5】:

对于第一个示例,编译器可能将字符串放入可写内存中,因此尝试更改时不会出现段错误。

对于第二个,编译器可能正在优化调用,或者可能正在优化调用本身,使其只是一个跳转(因为它是一个tail call),这意味着堆栈实际上并没有随着每个返回地址而增长调用,所以你可以无限期地递归。

但正如 Neil 在他的帖子中提到的,这些事情中的任何一个都会导致“未定义的行为”,因此在运行时不需要代码来生成 seg 错误。

【讨论】:

    【解决方案6】:

    维基百科的文章实际上列出了三种方法(其中一种是空指针解引用);你为什么不试试那个?

    至于为什么您尝试的两个示例没有,好吧,正如其他人所指出的那样,正确答案是它是未定义的行为,所以任何事情都可能发生(包括不发生段错误)。但是可以推测第一种形式对您来说没有失败的原因是因为您的编译器或系统对内存保护松懈。至于第二种情况,可以想象一个尾递归感知编译器可以优化无限递归 main 循环,并且最终不会溢出堆栈。

    【讨论】:

      【解决方案7】:

      我的单行风味:

      *(char *)0 = 0;
      

      【讨论】:

      • 即使这样也不能保证会产生段错误。这只是未定义的行为。哪个可能有效,也可能无效。
      • @Martin - 保证在所有平台上,不。这会导致 Ubuntu Linux 上出现段错误吗?是的。
      • @R:你不能保证!为什么你认为 Ubunt 如此特别!依靠未定义的行为来做一些特别的事情只是要求在未来的某个时候被烧得很厉害。出于某种原因,它被描述为未定义。除非您明确知道会发生什么并且您不这样做,否则不要这样做
      • 对于具有内存管理的平台,Linux 使对未映射页面的访问产生段错误。 Ubuntu 仅适用于具有内存管理的平台。 Linux 不映射第 0 页。在 Ubuntu 9.10 上,vm.mmap_min_addr 默认为 65535,因此您不会意外映射第 0 页。因此写入地址 0 会导致段错误。是的,如果你覆盖 vm.mmap_min_addr 然后 mmap page 0 然后写入地址 0 不会导致段错误,所以我同意它不是 100% 保证会导致段错误。但除此之外,在提到的特定平台上,什么时候不会导致段错误。
      • @RSamuelKlatchko 问题是编译器知道这是一个未定义的行为,所以它可能会假设它永远不会发生并且代码无法访问。一般来说,这意味着有多种可能的优化将导致上面的代码不会导致段错误,无论vm.mmap_min_addr 的值如何。
      【解决方案8】:

      产生段错误的方法很多。

      就像解除对坏指针的引用:

      char *s = (char *)0xDEADBEEF;
      *s = 'a';
      

      【讨论】:

      • 0xDEADBEEF 是一个数字。 “Hello World”是一个字符串。在这种情况下,s 最终指向内存地址0xDEADBEEF,这(很可能)完全无效。在 OP 的示例中,s 最终包含字符串的地址 - 如果编译器没有将内存声明为只读,那么如果我们尝试写入它,则不会产生错误。
      • 试试 0xBADBADBAD - 真的,真的,真的很糟糕
      【解决方案9】:
      int *hello = NULL;
      printf(*hello);
      

      或者你可以定义一个结构体(比如 HelloWorld 结构体)

      HelloWorld *myWorld = NULL;
      myWorld->world = "hello";
      

      【讨论】:

        【解决方案10】:
        char * ptr = 0;
        *ptr = 1;
        
        Segmentation fault
        

        【讨论】:

          猜你喜欢
          • 2021-03-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-20
          • 1970-01-01
          • 2016-12-05
          • 2012-01-15
          • 1970-01-01
          相关资源
          最近更新 更多