什么是使C++程序崩溃的最简单方法?

347

我正在尝试编写一个与另一个容易崩溃的进程(不在我的控制范围内)交互的Python程序。不幸的是,我正在交互的程序甚至不能可靠地崩溃!因此,我想制作一个快速的C++程序来有意地崩溃,但实际上我并不知道最好和最短的方法是什么,是否有人知道在这之间应该放些什么:

int main() {
    crashyCodeGoesHere();
}

为了让我的C++程序可靠地崩溃


6
您可以使用内联汇编来尝试执行特权指令:asm { cli; }; - Nate Koppenhaver
@aitchnyu 我认为每个问题的答案可用性存在差异。(顺便说一下:我没有为任何一个问题投票) - Andrew Barber
有没有在已经传播异常的情况下再抛出异常的评论?请检查我下面的答案并发表评论。 - Abhinav
6
Redis使用*((char*)-1) = 'x';代码来引发崩溃,以便进行调试。更多细节请参阅我的答案 - Shafik Yaghmour
我通过搜索测试用例来寻找一个崩溃报告系统的问题。我需要在正常运行时强制触发崩溃以调用崩溃报告和堆栈转储发送。谢谢! - Cory Trese
这个程序在所有系统上都应该始终崩溃。int *p = (int *)-1; *p = 1;。这是因为在任何系统上都不存在地址2 ^ 64 - 1,至少现在是这样(假设指针长度为8字节)。 - user16083509
31个回答

9
I see这里有很多答案,可能会在某些幸运的情况下完成工作,但它们没有一个是100%确定会崩溃的。一些会在某种硬件和操作系统上崩溃,而其他则不会。 然而,根据官方C ++标准,有一种标准方法可以使其崩溃。
引用自C ++标准< strong> ISO / IEC 14882 §15.1-7

If the exception handling mechanism, after completing the initialization of the exception object but before the activation of a handler for the exception, calls a function that exits via an exception, std::terminate is called (15.5.1).

struct C {
    C() { }
    C(const C&) {
        if (std::uncaught_exceptions()) {
            throw 0; // throw during copy to handler’s exception-declaration object (15.3)
        }
    }
};
int main() {
    try {
    throw C(); // calls std::terminate() if construction of the handler’s
    // exception-declaration object is not elided (12.8)
    } catch(C) { }
}

我写了一个小代码来演示这个问题,可以在Ideone这里找到并尝试。

class MyClass{
    public:
    ~MyClass() throw(int) { throw 0;}
};

int main() {
  try {
    MyClass myobj; // its destructor will cause an exception

    // This is another exception along with exception due to destructor of myobj and will cause app to terminate
     throw 1;      // It could be some function call which can result in exception.
  }
  catch(...)
  {
    std::cout<<"Exception catched"<<endl;
  }
  return 0;
}

ISO/IEC 14882 §15.1/9提到,没有try块的throw会导致隐式调用abort:

如果当前没有正在处理的异常,并且执行一个没有操作数的throw表达式,则调用std::terminate()

其他情况包括: 从析构函数中抛出: ISO/IEC 14882 §15.2/3


std::terminate() 在我的 C++ 应用程序中起作用(所以+1),但如果应用程序确实安装了非默认的 std::terminate_handler,那么情况可能就不那么简单了。默认的处理程序会引发 std::abort,它是可以被处理的,但对于 Windows PC 系统来说,我不清楚会发生什么... - SlySven

8
*( ( char* ) NULL ) = 0;

这会导致分段错误。

10
不能保证一定会崩溃。 - Windows programmer
23
“那会发生什么?”-- 任何事情都可能代替它。行为未定义,因此实现可能将0赋给程序的一个变量,也可能将42赋给程序的一个变量,或者格式化您的硬盘并继续执行您的程序。 - Windows programmer
7
(延续“Windows程序员”的思维方式)它可能会让你的电脑爆炸,或者让它活过来并接管人类。或者……它将在99.9%的情况下崩溃,并被定义为“未定义行为”,因为没有人愿意对此负责。 - Roee Gavirel
1
实际上,这甚至不能保证执行未定义的行为 - 它可能完全被定义并正常工作。考虑以下代码:http://pastebin.com/WXCtTiDD(在Linux上以root身份测试,如果进行一些配置更改,您也可以作为非root用户执行此操作http://wiki.debian.org/mmap_min_addr) - cha0site
2
@cha0site:根据标准,这肯定是未定义行为,因为这是对空指针进行解引用。无论您在Linux上观察到什么行为都可以归入“未定义行为”的范畴。 - Ben Voigt
显示剩余4条评论

7
这个缺失了:
int main = 42;

1
是的,它确实可以运行;但当你运行它时,它会做一些惊人的事情。 - Joshua

7

这段代码在我的Linux系统上崩溃了,因为字符串字面量存储在只读内存中:

0[""]--;

顺便说一句,g++拒绝编译这个。编译器变得越来越聪明 :)

6
这句话的意思是:如果使用死循环递归方法调用,会发生堆栈溢出吗?
#include <windows.h>
#include <stdio.h>

void main()
{
    StackOverflow(0);
}

void StackOverflow(int depth)
{
    char blockdata[10000];
    printf("Overflow: %d\n", depth);
    StackOverflow(depth+1);
}

请参见Microsoft知识库上的原始示例

4
一款足够智能的编译器如何避免优化掉未使用的堆栈分配和尾调用? - JB.
@JB:很遗憾,我不知道,因为我不熟悉现有编译器的优化逻辑。 - sll
8
使用 gcc 4.6.0 编译,并进行 -O2 及以上级别的优化,程序可以很好地被优化。只有当优化级别为 -O1 或更低时,才会导致分段错误。 - JB.
@Abhinav:请以C++为例,将所有这些方法都表达出来,然后发布您的答案 :) - sll

4
这是一个更可靠的abort版本,与上面的答案相比。它处理了sigabrt被阻止的情况。实际上,您可以使用任何默认操作为崩溃程序的信号代替abort。
#include<stdio.h>
#include<signal.h>
#include<unistd.h> 
#include<stdlib.h>
int main()
{
    sigset_t act;
    sigemptyset(&act);
    sigfillset(&act);
    sigprocmask(SIG_UNBLOCK,&act,NULL);
    abort();
}

4
这是Google在Breakpad中提供的代码片段。
  volatile int* a = reinterpret_cast<volatile int*>(NULL);
  *a = 1;

1
由于我正在测试Breakpad,这正是我想要的!我发现一些Breakpad minidumps没有产生指向导致崩溃的代码行的堆栈跟踪。而这个可以,所以我可以将其用作良好的p.o.c.测试。 - BuvinJ

3
int i = 1 / 0;

你的编译器可能会警告你,但在GCC 4.4.3下编译还是很好的。这可能会导致SIGFPE(浮点异常),与其他答案引起的SIGSEGV(内存分段违规)一样,在实际应用中不太可能发生,但仍然会导致崩溃。在我看来,这种方法更易读。
另一种方式(如果我们要作弊并使用signal.h)是:
#include <signal.h>
int main() {
    raise(SIGKILL);
}

这可保证杀死子进程,与SIGSEGV形成对比。


2
这并不保证会崩溃。 - Windows programmer
11
C++语言不能保证1/0会导致SIGFPE信号的发生,其行为是未定义的。实现可能会表示结果为42,但这并不确定。 - Windows programmer
1
@Windows程序员:关键是未定义的含义。如果我有一个部分函数f:A->B,且对于A中的某些a,f(a)未定义,则计算f(a)无法返回B中的值:要么f(a)不终止,要么它返回B之外的某些特殊值。返回B中的某个随机值b会使f(a)定义为f(a)= b,即不按照指定的方式计算f(f(a)必须未定义)。因此,计算1/0不能返回数字,因为在整数和实数上1/0都是未定义的。在我看来,在这里使用未定义行为(即任何内容都可以)不是正确的方法。 - Giorgio
2
@Giorgio 如果硬件没有自动捕获的方式,您仍然需要强制编译器发出至少两个指令,其中一个将是分支。这大约会使除法的成本翻倍。每个人都会像这样支付这个成本。如果它是可选的并且您想要它,您仍然可以使用库函数。如果它不是可选的并且您不想要它,您仍然会支付这个成本。 - Flexo
2
@Giorgio:我有一个应用程序,要执行100,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 * 10^1000000 次除法操作。尽管编译器无法知道这一点,但我确保其中没有任何一个是除以零的。 我绝对不希望编译器插入除以零检查。 - Martin York
显示剩余15条评论

3
int* p=0;
*p=0;

这也应该会崩溃。在Windows上,它会崩溃并显示AccessViolation,我想在所有操作系统上都应该是一样的。


5
在所有操作系统上,不,它不会在非受保护的操作系统(例如MS-DOS)中崩溃。实际上,有时候地址0处确实会有一些内容!对于x86实模式来说,中断向量表就在地址0处。 - ikh
它在Irix上没有崩溃。但当我们将那段代码移植到Linux时,我很遗憾地意识到我们甚至没有达到main()函数就已经崩溃了。 - Scheff's Cat
许多微控制器都有一个你应该写入数据的0号位置。即使是6502也属于这个类别。 - Jerry Jeremiah

3
尽管这个问题已经有了一个被接受的答案...
void main(){
    throw 1;
}

或者... void main(){throw 1;}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接