printf(str) 和 fwrite(str, 1, strlen(str), stdout) 之间有什么明显的区别吗?

Is there any observable difference between printf(str) and fwrite(str, 1, strlen(str), stdout)?

打印静态、无格式字符串时,C 编译器执行的常见优化之一是将 printf("foobar\n"); 等调用转换为等效的 puts("foobar");。只要不使用 return 值,这就是有效的(C 指定 printf returns 成功写入的字符数,但 puts 仅 returns 是成功的非负值)。 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 或任何更新标准的答案。

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

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