【问题标题】:Is there any observable difference between printf(str) and fwrite(str, 1, strlen(str), stdout)?printf(str) 和 fwrite(str, 1, strlen(str), stdout) 之间有什么明显的区别吗?
【发布时间】:2021-12-08 17:01:27
【问题描述】:

打印静态、无格式字符串时,C 编译器执行的常见优化之一是将printf("foobar\n"); 等调用转换为等效的puts("foobar");。只要不使用返回值,这是有效的(C 指定 printf 返回成功时写入的字符数,但 puts 仅在成功时返回非负值)。 C 编译器还会将 fprintf(stdout, "foobar") 之类的调用转换为 fwrite("foobar", 1, 6, stdout)

但是,printfputs 的优化仅适用于字符串以换行符结尾的情况,因为puts 会自动附加换行符。如果没有,我希望printf 可以优化为等效的fwrite,就像fprintf 的情况一样——但编译器似乎不这样做。比如下面的代码(Godbolt link):

#include <stdio.h>

int main() {
    printf("test1\n");
    printf("test2");
    fprintf(stdout, "test3");
}

在汇编中针对以下调用序列进行了优化:

puts("test1");
printf("test2");
fwrite("test3", 1, 5, stdout);

我的问题是:为什么编译器在没有终止换行符的情况下不将 printf 优化为 fwrite 或类似的?这仅仅是一个错过的优化,还是printffwrite 与静态、无格式字符串一起使用时存在语义差异?如果相关,我正在寻找适用于 C11 或任何更新标准的答案。

【问题讨论】:

  • “为什么编译器不优化......”更像是“为什么我测试的几个(1 或 2)个编译器没有优化......”。
  • 除了在前者中使用 stdout之外,我认为fprintf(stdout, "test3");fwrite("test3", 1, 5, stdout);printf("test3"); 相比没有任何理由。
  • 只是一个猜测,但我怀疑这个编译器优化领域已经很多年没有被重新审视了。我似乎记得(错误地)gcc 在没有'\n' 的情况下对文字进行了fputs() 优化。但是从 gcc 7 - 11 测试后,没有进行这样的优化。 printf -> puts 等的汇编器优化似乎是基本编译器选择,不受 -Ox 优化级别选择的影响。
  • ISTR 的 write 绕过了 puts 和 printf 完成的缓冲
  • @stark: POSIX write 有,但标准 C fwrite 没有。

标签: c printf language-lawyer compiler-optimization


【解决方案1】:

这只是一个错过的优化——编译器没有理由不能进行你想象的转换——但这是一个有动机的优化。编译器将对printf 的调用转换为对puts 的调用比对fputsfwrite 的调用要容易得多,因为后两者需要编译器提供stdout 作为参数. stdout 是一个宏,当编译器开始进行库调用优化时,宏定义可能不再可用(即使集成了预处理器),或者可能无法将令牌序列解析为 AST 片段没有了。

相比之下,编译器可以轻松地将fprintf 转换为fputs,因为它可以使用作为FILE* 参数提供给fprintf 的任何内容来调用fputs。但是我不会对可以将fprintf(stdout, "blah\n") 转换为fputs("blah\n", stdout) 转换为puts("blah") 的编译器感到惊讶......因为它无法知道@987654335 的第一个参数@调用标准输出。 (请记住,此优化过程适用于 &amp;_iob[1] 或类似的 IR。)

【讨论】:

    最近更新 更多