Компилятор меняет printf на put - PullRequest
3 голосов
/ 05 февраля 2020

Рассмотрим следующий код:

#include <stdio.h>

void foo() {
    printf("Hello world\n");
}

void bar() {
    printf("Hello world");
}

Сборка, созданная обеими этими двумя функциями:

.LC0:
        .string "Hello world"
foo():
        mov     edi, OFFSET FLAT:.LC0
        jmp     puts
bar():
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        jmp     printf

Теперь я знаю разницу между put и printf , но я нахожу это довольно интересным, поскольку g cc может проанализировать const char * и выяснить, вызывать ли printf или put.

Другая интересная вещь заключается в том, что в bar компилятор обнуляет регистр возврата (eax), даже если это функция void. Почему он сделал это там, а не в foo?

Правильно ли я предположил, что компилятор «изучил мою строку», или есть другое объяснение этого?

1 Ответ

7 голосов
/ 05 февраля 2020

Правильно ли я полагаю, что компилятор "изучил мою строку", или есть другое объяснение этого?

Да, именно это и происходит. Это довольно простая и обычная оптимизация, выполняемая компилятором.

Поскольку ваш первый вызов printf() просто:

printf("Hello world\n");

Это эквивалентно:

puts("Hello world");

Поскольку puts() не нужно сканировать и анализировать строку на предмет спецификаторов формата, это довольно быстро, чем printf(). Компилятор замечает, что ваша строка заканчивается новой строкой и не содержит спецификаторов формата, и поэтому автоматически преобразует вызов.

Это также экономит немного места, так как теперь нужно хранить только одну строку "Hello world" в полученном двоичном файле.

Обратите внимание, что это вообще невозможно для вызовов вида:

printf(some_var);

Если some_var не является простой константной строкой, компилятор не может знать, оно заканчивается на \n.

Другие распространенные оптимизации:

  • strlen("constant string") могут быть оценены во время компиляции и преобразованы в число.
  • memmove(location1, location2, sz) может преобразоваться в memcpy(), если компилятор уверен, что location1 и location2 не перекрываются.
  • memcpy() небольших размеров можно преобразовать в одной инструкции mov, и даже если размер больше, вызов иногда может быть встроен, чтобы быть быстрее.

Другая интересная вещь заключается в том, что в bar компилятор обнуляет регистр возврата ( eax) хотя тьфу, это void функция. Почему это было сделано там, а не в foo?

См. Здесь: Почему% eax обнуляется перед вызовом printf?


Похожие интересные посты

...