Ассемблер отлаживает выражение undefined

Я пытаюсь лучше понять, как компиляторы производят код для выражений undefined, например. для следующего кода:

int main()
{
    int i = 5;
    i = i++;
    return 0;
}

Это код ассемблера, сгенерированный gcc 4.8.2 (оптимизация выключена -O0, а Ive вставил мои собственные номера строк для справочных целей):

(gdb) disassemble main
Dump of assembler code for function main:
(1) 0x0000000000000000 <+0>:    push   %rbp
(2) 0x0000000000000001 <+1>:    mov    %rsp,%rbp
(3) 0x0000000000000004 <+4>:    movl   $0x5,-0x4(%rbp)
(4) 0x000000000000000b <+11>:   mov    -0x4(%rbp),%eax
(5) 0x000000000000000e <+14>:   lea    0x1(%rax),%edx
(6) 0x0000000000000011 <+17>:   mov    %edx,-0x4(%rbp)
(7) 0x0000000000000014 <+20>:   mov    %eax,-0x4(%rbp)
(8) 0x0000000000000017 <+23>:   mov    $0x0,%eax
(9) 0x000000000000001c <+28>:   pop    %rbp
(10) 0x000000000000001d <+29>:  retq   
End of assembler dump.

Выполнение этого кода приводит к значению i, оставшемуся при значении 5 (проверено с помощью оператора printf()), т.е. i, по-видимому, никогда не увеличивается. Я понимаю, что разные компиляторы будут оценивать/компилировать выражения undefined в differnet-путях, и это может быть просто так, как это делает gcc. Я мог бы получить другой результат с другим компилятором.

Что касается кода ассемблера, как я понимаю:

Игнорирование строки - 1-2 настройка указателей стека/базы и т.д. строка 3/4 - это значение 5 для i.

Может ли кто-нибудь объяснить, что происходит на линии 5-6? Похоже, что i будет в конечном счете переназначить значение 5 (строка 7), но это операция инкремента (необходимая для операции приращения post i++), просто оставленная/пропущенная компилятором в случае?

Ответ 1

Эти три строки содержат ваш ответ:

lea    0x1(%rax),%edx
mov    %edx,-0x4(%rbp)
mov    %eax,-0x4(%rbp)

Операция приращения не пропущена. lea - это приращение, принимающее значение от %rax и сохранение приращенного значения в %edx. %edx сохраняется, но затем перезаписывается следующей строкой, которая использует исходное значение из %eax.

Они помогают понять, что этот код должен знать, как работает lea. Он обозначает загружаемый эффективный адрес, поэтому, хотя он выглядит как разыменование указателя, на самом деле это просто математика, необходимая для получения окончательного адреса [независимо], а затем сохраняет адрес, а не значение по этому адресу. Это означает, что он может использоваться для любого математического выражения, которое может быть эффективно выражено с использованием режимов адресации в качестве альтернативы математическим кодам операций. По этой причине он часто используется как способ получить умножение и добавить в одну инструкцию. В частности, в этом случае он использовал для увеличения значения и перемещения результата в другой регистр в одной команде, где inc вместо этого перезаписывал бы его на месте.

Ответ 2

Строка 5-6 - это i++. lea 0x1(%rax),%edx i + 1 и mov %edx,-0x4(%rbp) записывает это обратно в i. Однако строка 7, mov %eax,-0x4(%rbp) записывает исходное значение обратно в i. Код выглядит так:

(4) eax = i
(5) edx = i + 1
(6) i = edx
(7) i = eax