printf(str) 和 fwrite(str, 1, strlen(str), stdout) 之间是否有任何可观察的区别?

6

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

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

#include <stdio.h>

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

在汇编语言中,gets被优化为以下调用序列:

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

我的问题是:在没有终止换行符的情况下,为什么编译器不会将printf优化为fwrite或类似函数?这只是一种被忽视的优化,还是printffwrite在使用静态、无格式字符串时存在语义差异?如果相关,请提供适用于C11或任何更新标准的答案。

“为什么编译器不优化…” 更像是 “为什么我测试的几个(1或2个)编译器没有进行优化…”。 - chux - Reinstate Monica
我认为 fprintf(stdout, "test3");fwrite("test3", 1, 5, stdout); 相比于 printf("test3"); 没有什么理由,除了前者使用了 stdout - chux - Reinstate Monica
只是猜测,但我怀疑编译器优化的这个领域已经很多年没有重新审视过了。我似乎记得(不正确地)gcc对没有'\n'的文字常量进行了fputs()优化。但在从gcc 7到11的测试后,没有进行这样的优化。printf-> puts等的汇编优化似乎是基础编译器选择,不受-Ox优化级别选择的影响。 - David C. Rankin
我记得write可以绕过puts和printf所做的缓冲。 - stark
@stark:POSIX的write可以,但标准C的fwrite不行。 - Nate Eldredge
1个回答

6

这只是一个错过的优化,没有理由为什么编译器不能执行您想象的转换,但它是一种有动机的。对于编译器来说,将对printf的调用转换为对puts的调用要容易得多,而将对fputsfwrite的调用转换为对stdout的调用则需要编译器提供stdout作为参数。 stdout是一个宏,在编译器完成库函数调用优化时,可能不再有宏定义(即使预处理器已经集成)或者可能无法将记号序列解析为AST片段。

相比之下,编译器可以很容易地将fprintf转换为fputs,因为它可以使用传递给fprintfFILE*参数来调用fputs。但我不会对能够将fprintf(stdout, "blah\n")转换为fputs("blah\n", stdout)但不能将其转换为puts("blah")的编译器感到惊讶...因为它无法知道此fprintf调用的第一个参数是否为stdout(请记住,此优化传递的是等效于&_iob[1]或类似的IR)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接