Инструкция CDQE
расширяет DWORD (32-битное значение) в регистре EAX
до QWORD (64-битное значение) в регистре RAX
.
MOVZX
инструкция обнуляет источник до пункта назначения.В этом случае он расширяет знак BYTE, загруженный из памяти в [rbp-528+rax]
, в регистр назначения DWORD, EAX
.
Инструкция XOR eax, 1
просто сбрасывает младший бит EAX
.Если в настоящее время установлено (1), то становится ясно (0).Если он в настоящее время очищен (0), то он становится установленным (1).
Что такое общая картина?Что ж, получается, что это почти совершенно бессмысленный код, тот тип вывода, который вы получаете от компилятора без включенной оптимизации.Бесполезно пытаться проанализировать это.
Но, если хотите, мы можем все равно проанализировать.Вот полный вывод сборки для вашего C-кода, сгенерированный GCC 8.2 в -O0
, с каждой аннотированной инструкцией:
main():
push rbp ; \ standard function
mov rbp, rsp ; / prologue code
sub rsp, 408 ; allocate space for stack array
mov DWORD PTR [rbp-8], 0 ; i = 0
.L4:
cmp DWORD PTR [rbp-8], 99 ; is i <= 99?
jg .L2 ; jump to L2 if i > 99; otherwise fall through
mov eax, DWORD PTR [rbp-8] ; EAX = i
cdqe ; RAX = i
movzx eax, BYTE PTR [rbp-528+rax] ; EAX = visited[i]
xor eax, 1 ; flip low-order bit of EAX (EAX ^= 1)
test al, al ; test if low-order bit is set?
je .L3 ; jump to L3 if low-order bit is clear (== 0)
; (which means it was originally set (== 1),
; which means visited[i] != 0)
; otherwise (visited[i] == 0), fall through
add DWORD PTR [rbp-4], 1 ; counter += 1
.L3:
add DWORD PTR [rbp-8], 1 ; i += 1
jmp .L4 ; unconditionally jump to top of loop (L4)
.L2:
mov eax, 0 ; EAX = 0 (EAX is result of main function)
leave ; function epilogue
ret ; return
Ни программист, ни оптимизирующий компилятор не будут производить этот код.Это крайне неэффективно использует регистры (предпочитая загружать и хранить в память , включая такие значения, как i
и counter
, которые являются основными целями для хранения в регистрах), и имеет многоиз бессмысленных инструкций.
Конечно, оптимизирующий компилятор действительно сделал бы число с этим кодом, полностью исключая его, поскольку он не имеет видимых побочных эффектов.Результат будет просто:
main():
xor eax, eax ; main will return 0
ret
Это не так интересно анализировать, но гораздо эффективнее.Вот почему мы платим нашим компиляторам C большие деньги.
Код C также имеет неопределенное поведение в следующих строках:
int counter;
/* ... */
counter=counter+1;
Вы никогда не инициализируете counter
, но затем пытаетесь прочитатьот него.Поскольку это переменная с автоматической продолжительностью хранения, ее содержимое не инициализируется автоматически, и чтение из неинициализированной переменной является неопределенным поведением.Это оправдывает компилятор C, испускающий любой код сборки, который ему нужен.
Давайте предположим, что counter
инициализируется в 0, и мы должны были написать этот код сборки вручную, игнорируя возможность исключения всего беспорядка.Мы получили бы что-то вроде:
main():
mov edx, OFFSET visited ; EDX = &visited[0]
xor eax, eax ; EAX = 0
MainLoop:
cmp BYTE PTR [rdx], 1 ; \ EAX += (*RDX == 0) ? 1
adc eax, 0 ; / : 0
inc rdx ; RDX += 1
cmp rdx, OFFSET visited + 100 ; is *RDX == &visited[100]?
jne MainLoop ; if not, keep looping; otherwise, done
ret ; return, with result in EAX
Что случилось?Соглашение о вызовах гласит, что EAX
всегда содержит возвращаемое значение, поэтому я поместил counter
в EAX
и предположил, что мы возвращаем counter
из функции.RDX
- указатель, отслеживающий текущую позицию в массиве visited
.Он увеличивается на 1 (размер байта) на протяжении MainLoop
.Помня об этом, остальная часть кода должна быть простой, за исключением инструкции ADC
.
Это инструкция сложения с переносом, используемая для записи условного if
внутри циклаbranchlessly.ADC
выполняет следующую операцию:
destination = (destination + source + CF)
, где CF
- флаг переноса.Инструкция CMP
прямо перед ней устанавливает флаг переноса, если visited[i] == 0
, а источник - 0
, так что она делает то, что я прокомментировал справа от инструкции: она добавляет 1 к EAX
(counter
) если *RDX == 0
(visited[i] == 0
);в противном случае он добавляет 0 (что не работает).
Если вы хотите написать разветвленный код, вы должны сделать:
main():
mov edx, OFFSET visited ; EDX = &visited[0]
xor eax, eax ; EAX = 0
MainLoop:
cmp BYTE PTR [rdx], 0 ; (*RDX == 0)?
jne Skip ; if not, branch to Skip; if so, fall through
inc eax ; EAX += 1
Skip:
inc rdx ; RDX += 1
cmp rdx, OFFSET visited + 100 ; is *RDX == &visited[100]?
jne MainLoop ; if not, keep looping; otherwise, done
ret ; return, with result in EAX
Это работает так же хорошо, но в зависимости отнасколько предсказуемы значения массива visited
, может быть медленнее из-за ошибки прогнозирования ветвления .