最短的 C 语言崩溃程序 (转)

  2013-06-03 00:00:00 CST

  Jingwen Peng

  C

想写个崩溃的 C 语言小程序,看起来是个奇怪的想法,不过在我曾经教过的一门实验课上,这是作业之一。 实际上,这是一件非常有教学意义的事情。

通常学生们要么尝试反向引用一个非法地址,要么就是除 0。除 0 会引发 SIGFPE 信号 (浮点异常)。这里有一个小例子程序,使用除零方法来使之崩溃:

1
2
3
int main() {
    return 1/0;
}

我们也可以删掉 return 关键字,但是当我这么做的时候 gcc 不会为这些语句生成可执行代码,即便优化选项被 disable 掉了。我们还可以通过把上面的语句改成赋值语句,使上面的代码改变一些特征:

1
2
3
4
i;
int main() {
    i = 1/0;
}

注意我声明了一个没有类型的 i。这样的代码在 C89 标准里是有效的,因为所有的声明都有隐形的缺省类型 int。在 C99 和其他一些C标准里这是一个错误。假定我们写的是 C89 代码,那么我们甚至可以使用隐形 int 来声明 main 函数:

1
2
3
4
i;
main() {
    i = 1/0;
}

那是相当短的代码了—如果我们不把用于缩进的空格计算进来,只有 16 个字符。然而,我们还可以做得更好! 当 C 程序在编译的时候,编译器会产生一个或更多对象文件,文件里有对于用到的库和全程对象 (函数和变量) 的符号索引。然后这些对象文件会被进行链接,这时符号索引被地址所代替,就产生了一个可执行文件。 编译器在一个对象文件里提供了一个调用 main 函数的入口点。调用 main 函数意味着我们试图执行在存储在 main 函数链接的位置所对应地址里的指令。 有趣的是,链接器对于不同对象的类型是没有概念的,它只知道它们的地址。所以,如果我们用一个常规的全程变量替换 main 函数,编译器会高兴地 build 对象文件,因为它不关心对象 main 的类型是什么;链接器也会高兴地链接它,因为它只关心 main 函数对应的地址。 所以,考虑这个 C 程序:

1
int main = 0;

这个程序会编译成一个可执行文件,它会试图调用地址 0,而 0 并不是我们能够访问的地址,这样我们会得到 SIGSEGV 信号(分段错误)。

更正:我前面关于这个程序崩溃的原因分析是错的。这个程序会试图按函数方式去执行 main,而这样不会奏效,因为编译器把它放到了不可执行的数据段。所以变量 main 初始化为什么值都无所谓了。 现在我们已经非常接近最小的崩溃的 C 程序了。我们可以利用这个技巧,配合隐形 int 类型,来把它进一步缩短。

1
main = 0;

还有,C 里的全局变量都会隐形地初始化为 0,所以上面的代码就等同于:

1
main;

好了,现在我们得到了最短的崩溃的 C 程序!

补充: Hacker News 用户 femto 指出,编译和链接一个空文件也是可能的。我没有发布这个是因为 gcc 会拒绝编译和链接这样的程序,它会要求分开编译和链接的过程。 另外,要是我们再学究一点,我应该指出我这里的 “全局” 变量意思是说 “静态” 变量。

如果您有疑问或建议,请在下方评论区域留言

遵循 BY-NC-ND 协议

评论功能加载中...