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

时间:2016-05-25 11:37:43

标签: c gcc assembly printf compiler-optimization

#include <stdio.h>
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

3 个答案:

答案 0 :(得分:11)

首先,问题不在于if;如您所见,gcc看到了if并设法将30直接传递给printf

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

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

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

    如果你问我,即使只是puts上面的%s\n优化已经&#34;去找样式点&#34;,除了人工测试用例之外,你不会真正获得任何优秀的表现。< / p>

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

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

对于好奇的读者,here是实际实施此优化的地方;正如你所看到的,该函数匹配了有限数量的非常简单的情况(除了GIMPLE之外,自从this nice article概述它们以来没有改变很多)。顺便提一下,源代码实际上解释了为什么他们无法为非换行情况实现stdout变体(在编译阶段没有简单的方法来引用(0,0)全局)

答案 1 :(得分:6)

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

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

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

答案 2 :(得分:1)

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

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

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

  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,存在小数点问题&#34;。&#34;是依赖于语言环境的。因此编译器无法将它扩展为字符串常量。虽然我们只讨论%d我在这里提到这一点是为了完整。