When printing static, format-less strings, one of the common optimizations that C compilers perform is to transform calls like printf("foobar\n");
to the equivalent puts("foobar");
. This is valid as long as the return value is not used (C specifies that printf
returns the number of characters written on success, but that puts
only returns a non-negative value on success). C compilers will also transform calls like fprintf(stdout, "foobar")
to fwrite("foobar", 1, 6, stdout)
.
However, the printf
to puts
optimization only applies if the string ends with a newline, since puts
automatically appends a newline. When it does not, I would expect that printf
could be optimized to an equivalent fwrite
, just like the fprintf
case - but it seems like compilers don't do this. For example, the following code (Godbolt link):
#include <stdio.h>
int main() {
printf("test1\n");
printf("test2");
fprintf(stdout, "test3");
}
gets optimized to the following sequence of calls in assembly:
puts("test1");
printf("test2");
fwrite("test3", 1, 5, stdout);
My question is: why do compilers not optimize printf
to fwrite
or similar in the absence of a terminating newline? Is this simply a missed optimization, or is there a semantic difference between printf
and fwrite
when used with static, format-less strings? If relevant, I am looking for answers that would apply to C11 or any newer standard.
CodePudding user response:
This is just a missed optimization—there's no reason why a compiler could not do the transform you imagine—but it's a motivated one. It is significantly easier for a compiler to convert a call to printf
into a call to puts
than a call to fputs
or fwrite
, because the latter two would require the compiler to supply stdout
as an argument. stdout
is a macro, and by the time the compiler gets around to doing library-call optimizations, macro definitions may no longer be available (even if the preprocessor is integrated) or it may not be possible to parse token sequences into AST fragments anymore.
In contrast, the compiler can easily turn fprintf
into fputs
, because it can use whatever was supplied as the FILE*
argument to fprintf
to call fputs
with as well. But I would not be surprised by a compiler that could turn fprintf(stdout, "blah\n")
into fputs("blah\n", stdout)
but not into puts("blah")
... because it has no way of knowing that the first argument to this fprintf
call is stdout. (Keep in mind that this optimization pass is working with the IR equivalent of &_iob[1]
or some such.)