Почему% eax обнуляется перед вызовом printf? - PullRequest
27 голосов
/ 02 июня 2011

пытаюсь немного подцепить х86.Я собираю на 64-битном Mac с gcc -S -O0.

Код в C:

printf("%d", 1);

Вывод:

movl    $1, %esi
leaq    LC0(%rip), %rdi
movl    $0, %eax        ; WHY?
call    _printf

Я не понимаю, почемуЗначение eax очищается до 0, прежде чем вызывается printf.Так как printf возвращает количество напечатанных символов в %eax, я думаю, он обнуляется, чтобы подготовить его к printf, но я бы предположил, что printf должен будет отвечать за его подготовку.Кроме того, напротив, если я вызываю свою собственную функцию int testproc(int p1), gcc не видит необходимости готовить %eax.Поэтому мне интересно, почему gcc по-разному относится к printf и testproc.

Ответы [ 2 ]

36 голосов
/ 02 июня 2011

В ABI x86_64, если функция имеет переменные аргументы, то AL (который является частью EAX), как ожидается, будет содержать число векторных регистров, используемых для хранения аргументов этой функции.

В вашем примере:

printf("%d", 1);

имеет целочисленный аргумент, поэтому нет необходимости в векторном регистре, следовательно, AL имеет значение 0.

С другой стороны, если вы измените свойНапример:

printf("%f", 1.0f);

, затем литерал с плавающей точкой сохраняется в векторном регистре и, соответственно, AL устанавливается в 1:

movsd   LC1(%rip), %xmm0
leaq    LC0(%rip), %rdi
movl    $1, %eax
call    _printf

Как и ожидалось:

printf("%f %f", 1.0f, 2.0f);

заставит компилятор установить AL в 2, так как есть два аргумента с плавающей точкой:

movsd   LC0(%rip), %xmm0
movapd  %xmm0, %xmm1
movsd   LC2(%rip), %xmm0
leaq    LC1(%rip), %rdi
movl    $2, %eax
call    _printf

Что касается других ваших вопросов:

puts также обнуляет %eax прямо перед вызовом, хотя он принимает только один указатель.Почему это?

Не должно.Например:

#include <stdio.h>

void test(void) {
    puts("foo");
}

при компиляции с gcc -c -O0 -S, выходные данные:

pushq   %rbp
movq    %rsp, %rbp
leaq    LC0(%rip), %rdi
call    _puts
leave
ret

и %eax не обнуляются.Однако если вы удалите #include <stdio.h>, то полученная сборка обнулит %eax прямо перед вызовом puts():

pushq   %rbp
movq    %rsp, %rbp
leaq    LC0(%rip), %rdi
movl    $0, %eax
call    _puts
leave
ret

Причина связана с вашим вторым вопросом:

Это также происходит перед любым вызовом моей собственной функции void proc () (даже с установленным параметром -O2), но она не обнуляется при вызове функции void proc2 (int param).

Есликомпилятор не видит объявление функции, тогда он не делает никаких предположений о ее параметрах, и функция вполне может принимать переменные аргументы.То же самое относится и к тому, что вы указываете пустой список параметров (чего не следует делать, а ISO / IEC помечает его как устаревшую функцию C).Поскольку компилятор не имеет достаточной информации о параметрах функции, он обнуляет %eax перед вызовом функции, потому что это может быть случай, когда функция определена как имеющая переменные аргументы.

Например:

#include <stdio.h>

void function() {
    puts("foo");
}

void test(void) {
    function();
}

, где function() имеет пустой список параметров, в результате:

pushq   %rbp
movq    %rsp, %rbp
movl    $0, %eax
call    _function
leave
ret

Однако, если вы будете следовать рекомендованной практике, указав void, когда функция не принимает параметров,такие как:

#include <stdio.h>

void function(void) {
    puts("foo");
}

void test(void) {
    function();
}

, тогда компилятор знает, что function() не принимает аргументы - в частности, он не принимает переменные аргументы - и, следовательно, не очищает %eax перед вызовом этой функции:

pushq   %rbp
movq    %rsp, %rbp
call    _function
leave
ret
25 голосов
/ 02 июня 2011

Из x86_64 System V ABI :

Register    Usage
%rax        temporary register; with variable arguments
            passes information about the number of vector
            registers used; 1st return register
...

printf - это функция с переменными аргументами, а число используемых векторных регистров равно нулю.

Обратите внимание, что printf должен проверять только %al, потому что вызывающей стороне разрешено оставлять мусор в старших байтах %rax.(Тем не менее, xor %eax,%eax является наиболее эффективным способом обнуления %al)

См. этот вопрос и ответ и тег * вики для получения дополнительной информации или дляпоследние ссылки ABI, если вышеуказанная ссылка устарела.

...