Al imprimir cadenas estáticas sin formato, una de las optimizaciones comunes que realizan los compiladores de C es transformar llamadas como printf("foobar\n"); al equivalente puts("foobar"); . Esto es válido siempre que no se use el valor devuelto (C especifica que printf devuelve el número de caracteres escritos en caso de éxito, pero que puts solo devuelve un valor no negativo en caso de éxito). Los compiladores de C también transformarán llamadas como fprintf(stdout, "foobar") a fwrite("foobar", 1, 6, stdout) .
Sin embargo, la optimización de printf para puts solo se aplica si la cadena termina con una nueva línea, ya que puts agrega automáticamente una nueva línea. Cuando no es así, esperaría que printf pudiera optimizarse a un fwrite equivalente, al igual que en el caso de fprintf , pero parece que los compiladores no hacen esto. Por ejemplo, el siguiente código ( enlace Godbolt ):
#include <stdio.h> int main() { printf("test1\n"); printf("test2"); fprintf(stdout, "test3"); }se optimiza para la siguiente secuencia de llamadas en ensamblaje:
puts("test1"); printf("test2"); fwrite("test3", 1, 5, stdout); Mi pregunta es: ¿por qué los compiladores no optimizan printf a fwrite o similar en ausencia de una nueva línea de terminación? ¿Es esto simplemente una optimización perdida, o hay una diferencia semántica entre printf y fwrite cuando se usa con cadenas estáticas sin formato? Si es relevante, estoy buscando respuestas que se apliquen a C11 o cualquier estándar más nuevo.
Esta es solo una optimización perdida, no hay razón por la cual un compilador no pueda hacer la transformación que imagina, pero es una optimización motivada. Es significativamente más fácil para un compilador convertir una llamada a printf en una llamada a puts que una llamada a fputs o fwrite , porque las dos últimas requerirían que el compilador proporcione stdout como argumento. stdout es una macro, y para cuando el compilador comience a realizar optimizaciones de llamada de biblioteca, es posible que las definiciones de macro ya no estén disponibles (incluso si el preprocesador está integrado) o que ya no sea posible analizar secuencias de tokens en fragmentos AST.
Por el contrario, el compilador puede convertir fácilmente fprintf en fputs , ya que también puede usar lo que se suministró como argumento FILE* a fprintf para llamar a fputs . Pero no me sorprendería un compilador que pudiera convertir fprintf(stdout, "blah\n") en fputs("blah\n", stdout) pero no en puts("blah") ... porque no tiene manera de saber que el primer argumento de esta llamada fprintf es stdout. (Tenga en cuenta que este pase de optimización funciona con el equivalente IR de &_iob[1] o algo similar).