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)
.
但是,printf
到 puts
优化仅适用于字符串以换行符结尾的情况,因为 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
或类似的?这仅仅是错过了优化,还是 printf
和 fwrite
与静态、无格式字符串一起使用时存在语义差异?如果相关,我正在寻找适用于 C11 或任何更新标准的答案。
这只是一个遗漏的优化——没有理由编译器不能完成你想象的转换——但这是一个有动机的优化。编译器将对 printf
的调用转换为对 puts
的调用比对 fputs
或 fwrite
的调用要容易得多,因为后两者需要编译器提供 stdout
作为参数。 stdout
是一个宏,当编译器着手进行库调用优化时,宏定义可能不再可用(即使集成了预处理器)或者可能无法将标记序列解析为AST 片段了。
相比之下,编译器可以轻松地将 fprintf
转换为 fputs
,因为它可以使用作为 fprintf
的 FILE*
参数提供的任何内容来调用 fputs
同为好。但是我不会对可以将 fprintf(stdout, "blah\n")
变成 fputs("blah\n", stdout)
但 而不是 变成 puts("blah")
的编译器感到惊讶......因为它无法知道此 fprintf
调用 的第一个参数是 标准输出。 (请记住,此优化过程适用于 &_iob[1]
等值的 IR。)
打印静态、无格式字符串时,C 编译器执行的常见优化之一是将 printf("foobar\n");
等调用转换为等效的 puts("foobar");
。只要不使用 return 值,这就是有效的(C 指定 printf
returns 成功写入的字符数,但 puts
仅 returns 是成功的非负值)。 C 编译器还会将 fprintf(stdout, "foobar")
之类的调用转换为 fwrite("foobar", 1, 6, stdout)
.
但是,printf
到 puts
优化仅适用于字符串以换行符结尾的情况,因为 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
或类似的?这仅仅是错过了优化,还是 printf
和 fwrite
与静态、无格式字符串一起使用时存在语义差异?如果相关,我正在寻找适用于 C11 或任何更新标准的答案。
这只是一个遗漏的优化——没有理由编译器不能完成你想象的转换——但这是一个有动机的优化。编译器将对 printf
的调用转换为对 puts
的调用比对 fputs
或 fwrite
的调用要容易得多,因为后两者需要编译器提供 stdout
作为参数。 stdout
是一个宏,当编译器着手进行库调用优化时,宏定义可能不再可用(即使集成了预处理器)或者可能无法将标记序列解析为AST 片段了。
相比之下,编译器可以轻松地将 fprintf
转换为 fputs
,因为它可以使用作为 fprintf
的 FILE*
参数提供的任何内容来调用 fputs
同为好。但是我不会对可以将 fprintf(stdout, "blah\n")
变成 fputs("blah\n", stdout)
但 而不是 变成 puts("blah")
的编译器感到惊讶......因为它无法知道此 fprintf
调用 的第一个参数是 标准输出。 (请记住,此优化过程适用于 &_iob[1]
等值的 IR。)