Почему режим отладки MSVC опускает cmp / jcc для одного пустого тела if (), а не для другого (i ++ vs. ++ i)? - PullRequest
0 голосов
/ 05 апреля 2019

Я использую компьютер AMD64 (Intel Pentium Gold 4415U) для сравнения некоторых инструкций по сборке, преобразованных из языка C (конечно, точно, разборки).

В Windows 10 я использовал Visual Studio 2017 (15.2) с их компилятором Си.Мой пример кода показан ниже:

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

Разборка показывает, как показано ниже:

mov         eax,dword ptr [i]  // if (++i == 4);
inc         eax  
mov         dword ptr [i],eax  

mov         eax,dword ptr [i]  // if (i++ == 4);
mov         dword ptr [rbp+0D4h],eax    ; save old i to a temporary
mov         eax,dword ptr [i]  
inc         eax  
mov         dword ptr [i],eax  
cmp         dword ptr [rbp+0D4h],4      ; compare with previous i
jne         main+51h (07FF7DDBF3601h)  
mov         dword ptr [rbp+0D8h],1  
jmp         main+5Bh (07FF7DDBF360Bh)  
*mov         dword ptr [rbp+0D8h],0

07FF7DDBF3601 переходит к последней строке инструкции (*).
07FF7DDBF360B переходит к 'return0; '.

В if (++i == 4) программа не наблюдает, удовлетворяет ли условие «добавлено» i.

Однако в if (i++ == 4) программа сохраняет «предыдущее» значение i.в стек, а затем делает приращение.После этого программа сравнивает «предыдущий» i с постоянным целым числом 4.

В чем причина различия двух кодов C?Это просто механизм компилятора?Будет ли он отличаться от более сложного кода?

Я пытался найти об этом в Google, однако мне не удалось найти причину разницы.Должен ли я понять «Это просто поведение компилятора»?

1 Ответ

2 голосов
/ 05 апреля 2019

Как говорит Пол, программа не имеет видимых побочных эффектов, и с включенной оптимизацией MSVC или любой из других основных компиляторов (gcc / clang / ICC) скомпилирует main в простой xor eax,eax / ret.Значение

i никогда не выходит за пределы функции (не сохраняется в глобальном или возвращаемом), поэтому его можно полностью оптимизировать.И даже если бы это было так, постоянное распространение здесь тривиально.


Это просто изюминка / деталь реализации, которую анти-оптимизированный код MSVC в режиме отладки решает не испускать cmp/jcc надпустое if тело;даже в режиме отладки это не поможет при отладке.Это будет инструкция перехода, которая переходит на тот же адрес, на который она падает.

Смысл кода режима отладки в том, что вы можете пошагово пройти по исходным строкам и изменить Cпеременные с отладчиком.Не то чтобы asm был буквальной и точной транслитерацией C в asm.(А также то, что компилятор генерирует его быстро, не затрачивая усилий на качество, для ускорения циклов редактирования / компиляции / запуска.) Почему clang выдает неэффективный asm с -O0 (для этой простой суммы с плавающей запятой)?

Точность определения кода компилятора не зависит ни от каких языковых правил;не существует реальных стандартов, которые бы определяли, что компиляторы должны делать в режиме отладки, если фактически использовать инструкцию перехода для пустого if тела.


Как видно из версии вашего компилятора, i++ post-increment было достаточно, чтобы компилятор забыл, что тело цикла пустое?

Я не могу воспроизвести ваш результат с MSVC 19.0 или 19.10 в проводнике компилятора Godbolt с 32или 64-битный режим .(VS2015 или VS2017).Или любая другая версия MSVC.Я не получаю никаких условных переходов от MSVC, ICC или gcc.

MSVC реализует i++ с фактическим сохранением в памяти для старого значения, как вы показываете, хотя.Так ужасно.GCC -O0 делает значительно более эффективный код режима отладки.Конечно, все еще довольно умная смерть, но в одном утверждении это иногда намного менее вредно.

Я могу воспроизвести его с помощью лязга, однако!(Но он разветвляется на оба if с):

# clang8.0 -O0
main:                                   # @main
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], 0       # default return value

        mov     dword ptr [rbp - 8], 0       # int i=0;

        mov     eax, dword ptr [rbp - 8]
        add     eax, 1
        mov     dword ptr [rbp - 8], eax
        cmp     eax, 4                       # uses the i++ result still in a register
        jne     .LBB0_2                      # jump over if() body
        jmp     .LBB0_2                      # jump over else body, I think.
.LBB0_2:

        mov     eax, dword ptr [rbp - 8]
        mov     ecx, eax
        add     ecx, 1                       # i++ uses a 2nd register
        mov     dword ptr [rbp - 8], ecx
        cmp     eax, 4
        jne     .LBB0_4
        jmp     .LBB0_4
.LBB0_4:

        xor     eax, eax                     # return 0

        pop     rbp                          # tear down stack frame.
        ret
...