Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 469
nneonneo Avatar asked Oct 22 '21 00:10

nneonneo


1 Answers

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.)

like image 96
zwol Avatar answered Oct 23 '22 04:10

zwol