c - IOCCC 1986/wall.c - 为什么 TCC 在处理早期 C 代码方面击败了 GCC?

标签 c debugging gcc gdb tcc

IOCCC 早期的另一颗珍珠是 Larry Wall 1986 年的条目:

http://www.ioccc.org/years.html#1986 (墙)

我怀疑现在没有 C 编译器可以真正直接编译该源代码,因为它包含严重的预处理器滥用:

  • 最新 TDM-GCC 9.2.0 设置为 ANSI 模式失败
  • 上次 TCC 0.9.27 失败

然而,在从混淆的原始代码中提取出预处理后的代码(始终使用 GCC 的 cpp -traditional)后,TCC 和 GCC 都设法对其进行编译;尽管如此,GCC 的努力都白费了,因为当程序试图开始解码其混淆的介绍文本时,程序会卡住(对于那些想要深入研究的人来说,这里不会破坏它!)

另一方面,TCC 成功地对 system()read()write() 的隐式声明发出警告。并快速生成工作程序。

我尝试使用 GDB 单步执行 GCC 代码,这就是我发现编译的 GCC 代码在 for 循环的第二遍中阻塞的原因,该循环遍历文本字符串以进行解码它:

[Inferior 1(进程 9460)退出,代码为 030000000005]

该进程 ID 并不重要,因为它代表崩溃的调试构建可执行文件。 但是,退出代码保持不变。

显然,TCC 比 GCC 更适合 IOCCC 条目。后者仍然能够成功编译甚至运行一些条目,但对于像这样的棘手情况,TCC 很难被击败。它唯一的缺点是,在预处理极其滥用的代码(例如本示例)时,它会表现不佳。它在某些预处理条目之间留下了空格,因此无法将它们连接到作者想要的 C 关键字中,而 GCC 的 cpp 可以 100% 工作。

我的问题是,听起来很哲学,甚至是修辞:

与 TCC 不同,现代 GCC 中是什么导致它无法编译,或者在编译早期的 C 程序时生成不可用的代码?

提前感谢所有反馈,非常感谢!

注意:我使用的是带有 WSL 2 的 Windows 10 版本 2004; GCC 在 Windows 和 WSL 2 环境中都会失败。我也计划在 WSL 2 中编译 TCC,以便在该环境中进行比较。

PS:当这个程序最终按预期执行时,我非常喜欢它。毫无疑问,当之无愧的是当年的“困惑中最全面的大奖”!

最佳答案

What is it in modern GCC that makes it either fail to compile, or produce unusable code when it does compile, earlier C programs, unlike TCC?

未定义的行为。这更像是一条规则。看看this classic 1984 entry .


现在的 C 编译器按照 ISO 9899 标准中的规定来编译 C,该标准的第一个修订版于 1990 年(或 1989 年)发布。该计划早于该计划。值得注意的是,它使用了一些非常奇怪的传统预处理器语法,这些语法在 C89、C99、C11 等中无效。

一般的想法是,默认情况下您不希望允许此语法,因为传统预处理器不会生成与现代预处理器兼容的代码 - 例如,传统预处理器也会替换字符串内的宏:

#define greeting(thing) puts("Hello thing")
main() {
    greeting(world!!!);
}

预处理到

main() {
    puts("Hello world!!!");
}

该程序有效的C89,尽管风格不好;但它会预处理为

main() {
    puts("Hello thing");
}

因此,最好在出现任何非标准预处理器使用迹象时就出错,否则代码可能会被巧妙地破坏,因为不会进行此类替换。


另一件事是可写字符串。反混淆代码直接尝试修改字符串文字。 C89 指定这具有未定义的行为 - 这些会导致崩溃,因为它们映射到 GCC 编译的程序中的只读页面中。较旧的 GCC 版本支持 -fwriteable-strings 但它很久以前就被弃用了,因为无论如何它都有 bug。


我通过 GCC 9.3.0 的这些最小更改使程序运行起来。 -traditional 不再支持编译,因此您必须先进行预处理,然后再进行编译:

gcc -traditional -E wall.c > wall_preprocessed.c

perl -pi -e '/^[^#]/ && s/(".*?")/(char[]){$1}/g'  wall_preprocessed.c
# thanks Larry ;)

gcc wall_preprocessed.c

即我将所有看起来像字符串文字 "..." 且不在编译器行指令(以 # 开头的行)内的内容包装到 (char []){"..."} 数组复合文字 - 众所周知,复合文字具有作用域存储持续时间,非常量限定字面量是可写的。

关于c - IOCCC 1986/wall.c - 为什么 TCC 在处理早期 C 代码方面击败了 GCC?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64199950/

相关文章:

C - 如何将数组指针设置回第一个内存字节

比较两个整数并在两个数字之间插入比较符号 <,>=

c - 将一个 .so 与另一个 .so 链接时 undefined symbol

debugging - 如何配置 Aptana 3 以在 Debug模式下运行我的 Rails 服务器,以便它在断点处停止?

c - 启用优化的奇怪行为

c - 链接到 gobject-introspection 库时出现问题

c - 编译器是否将此逻辑操作作为函数处理?

c++ - 控制台中的 OutputDebugString()

c++ - Visual Studio 调试 - 将数组转储到文件

c - 如何取消任何先前的名称定义,在 gcc 中内置或提供 ‘-D’ 选项