为什么GCC没有优化对printf的这个调用?

#include  int main(void) { int i; scanf("%d", &i); if(i != 30) { return(0); } printf("i is equal to %d\n", i); } 

结果字符串似乎总是“我等于30”,那么,为什么GCC不会通过调用puts()write()来优化对printf的调用?

(刚刚使用gcc -O3 (版本5.3.1)或Godbolt Compiler Explorer检查生成的程序集)

首先,问题不在于if ; 如你所见, gcc通过if看到并设法直接传递给printf

现在, gcc确实有一些逻辑来处理printf特殊情况(特别是,它确实优化了printf("something\n")甚至printf("%s\n", "something") to puts("something") ),但它非常具体,并没有进一步发展; printf("Hello %s\n", "world")保持原样。 更糟糕的是,上面没有尾随换行符的任何变体都保持不变,即使它们可以转换为fputs("something", stdout)

我想这归结为两个主要问题:

  • 上面的两个案例是非常容易实现的模式,并且经常发生,但对于其他情况,它可能很少值得付出努力; 如果字符串是常量且性能很重要,程序员可以轻松地处理它 – 实际上,如果printf的性能很关键,他不应该依赖于这种优化,这种优化可能会在格式上稍有变化时中断串。

    如果你问我,即使只是上面的puts优化已经“追求风格点”,除了人工测试用例之外,你不会真正获得任何重要的表现。

  • 当你开始超越%s\nprintf是一个雷区,因为它对运行时环境有很强的依赖性; 特别是,许多printf说明符(不幸的是)受到语言环境的影响,而且还有一个特定于实现的怪癖和说明符的提升( gcc可以与glibc,musl,mingw / msvcrt,…中的printf一起使用 – 并且在编译时时间你不能调用目标C运行时 – 想想你何时交叉编译)。

    我同意这个简单的%d案例可能是安全的,但我可以理解为什么他们可能决定避免过于聪明并且只在这里执行最愚蠢和最安全的优化。


对于好奇的读者来说,这是实际实现这种优化的地方; 正如你所看到的,该函数匹配了有限数量的非常简单的情况(除了GIMPLE之外,由于编写了这篇很好的文章而没有改变很多)。 顺便提一下,源实际上解释了为什么他们无法为非换行情况实现fputs变体(在编译阶段没有简单的方法来引用stdout全局)。

现代编译器非常聪明,但不够聪明,不能用逻辑来预测输出。 在这种情况下,人类程序员很容易优化这段代码,但这项任务对于机器来说太难了。 事实上,预测程序的输出而不运行它是不可能的程序(例如gcc)。 为了certificate,请参阅暂停问题 。

无论如何,你不希望没有输入的所有程序都被优化为几个puts()语句,因此GCC不优化包含一个scanf()语句的代码是完全合理的。


但是,这并不意味着编译器无法或不应该进行优化以生成更优化的执行文件。 虽然不可能预测所有程序的结果,但完全可能并希望改进其中的许多程序

不确定这是否是一个令人信服的答案,但我希望编译器不应该优化printf("%d\n", 10) case to puts("10")

为什么? 因为这种情况可能比你想象的更复杂。 以下是我目前可以想到的一些问题:

  1. 将二进制数转换为ASCII会增加字符串文字的大小 ,从而增加整体代码大小。 虽然这与小数字无关,但如果它是printf("some number: %d", 10000) —- 5位数或更多(假设int是32位),字符串大小增加将超过保存的大小整数,有些人可以认为这是一个缺点。 是的,通过转换,我保存了“push to stack”指令,但是指令的字节数和保存的数量是特定于体系结构的。 编译器说它是否值得,这是非常重要的。

  2. 如果在格式中使用填充 ,也可以增加扩展字符串文字的大小。 示例: printf("some number: %10d", 100)

  3. 有时我开发人员会在printf调用中共享一个格式字符串,原因如下:

     printf("%-8s: %4d\n", "foo", 100); printf("%-8s: %4d\n", "bar", 500); printf("%-8s: %4d\n", "baz", 1000); printf("%-8s: %4d\n", "something", 10000); 

    将它们转换为不同的字符串文字可能会失去大小优势。

  4. 对于%f%e%g ,存在小数点“。”的问题。 是依赖于语言环境的。 因此编译器无法将它扩展为字符串常量。 虽然我们只讨论%d我在这里提到这一点是为了完整性。