2016-09-18 8 views
0

я иногда использую этот шаблон, чтобы перебирать массив что-то:как проверить состояние правильно: Je или JGE

mov [rsp+.r12], r12 ; Choose a register that calls inside the loop won't modify 
    mov r12, -1 
.i: 
    inc r12 
    cmp r12, [rbp-.array_size] 
    je .end_i 
    ; ... program logic ... 
    jmp .i 
.end_i: 
    mov r12, [rsp+.r12] 

Я понимаю, что это достаточно для проверки равенства, но не должен один «надежно» тест на " больше или равно "(предотвратите ситуацию, которая не произойдет).

Следует ли использовать je или jge в этих случаях?

Я спрашиваю о конкретном кончике, который может уменьшить вероятность введения ошибок.

+0

Мне всегда нравилась идея тестирования диапазона, а не просто для равенства, в случае, если бит случайно или случайно. Но в x86 asm имейте в виду, что 'cmp/jge' не может замаскироваться на Core2 (в 32-битном режиме), но' cmp/je' может. Я подумал, что это будет более актуально, пока я не проведу проверку и не обнаружу, что это был только Core2, а не Nehalem, который не мог свести это на нет, поскольку макро-fusion не работает вообще в 64-битном режиме на Core2. (В более поздних микроархитектурах этого ограничения нет и можно комбинировать все больше и больше комбинаций.) –

+0

Почему вы показываете какой-то странный разлив/перезагрузку r12, чтобы освободить его для использования в качестве временного счетчика? Это абсолютно неуместно (и не похоже на эффективный код). Конечно, есть какой-то реестр, который уже мертв, который можно использовать без сохранения. –

+0

@Peter, как его правильно записать? Я думаю, что r12 - хороший выбор, потому что вызовы функций внутри цикла, такие как printf, не будут изменять регистр r12, сохраненный во время вызова, и нам не нужно вручную сохранять и восстанавливать счетчик вокруг вызовов.Пожалуйста, исправьте, если это неправильно. –

ответ

3

Мне всегда нравилась идея тестирования диапазона, а не просто для равенства, в случае, если бит случайно или что-то случится. Имейте в виду, что cmp/jge не может macro-fuse на Core2 (в 32-битном режиме), но cmp/je может. Я думал, что это будет более актуальным до тех пор, пока я не проведу Agner Fog's microarch pdf и не обнаружу, что это был только Core2, а не Nehalem, который не мог свести его на нет, поскольку макро-fusion вообще не работает в 64-битном режиме на Core2. (Более поздние микроархитектуры не имеют такого ограничения и могут создавать макро-предохранители все больше и больше.)

В зависимости от счетчика, вы можете обычно считать без CMP вообще (dec/jnz). И часто вы знаете, что он не должен быть 64-битным, поэтому вы можете использовать dec esi/jnz или что угодно. dec esi/jge действительно работает для подписанных счетчиков, но dec не устанавливает CF, поэтому вы не можете (полезно) использовать JA.

Ваша структура цикла, с if() break в середине и jmp в конце, не является идиоматической для asm. Нормальный:

mov ecx, 100 

.loop:    ; do{ 
    ;; stuff 
    dec ecx 
    jge .loop  ; }while(--ecx >= 0) 

Вы можете использовать JG только перезапустить цикл с положительной ECX, то есть петли из 100..1 вместо 100..0.

Наличие незанятой условной ветви и Принимаемая безусловная ветвь в петле менее эффективна.


Расширение на обсуждение в вопросе комментарии о сохранении/восстановлении r12: Обычно вы могли бы сделать что-то вроде:

my_func: 
    ; push rbp 
    ; mov rbp, rsp  ; optional: make a stack frame 

    push rbx   ; save the caller's value so we can use it 
    sub rsp, 32  ; reserve some space 

    imul edi, esi, 11 ; calculate something that we want to pass as an arg to foo 
    mov ebx, edi  ; and save it in ebx 
    call foo 
    add eax, ebx  ; and use value. If we don't need the value in rbx anymore, we can use the register for something else later. 

    ... ;; calculate an array size in ecx 

    test ecx, ecx    ; test for the special case of zero iterations *outside* the loop, instead of adding stuff inside. We can skip some of the loop setup/cleanup as well. 
    jz .skip_the_loop 

    ; now use rbx as a loop counter 
    mov ebx, ecx 
.loop: 
    lea edi, [rbx + rbx*4 + 10] 
    call bar      ; bar(5*ebx+10); 
    ; do something with the return value? In real code, you would usually want at least one more call-preserved register, but let's keep the example simple 
    dec ebx 
    jnz .loop 
.skip_the_loop: 

    add rsp, 32   ; epilogue 
    pop rbx 

    ;pop rbp    ; pointless to use LEAVE; rsp had to already be pointing to the right place for POP RBX 
    ret 

Обратите внимание, как мы используем RBX на пару вещей внутри функции, но только сохранить/восстановить его один раз.

+1

Довольно много моих мыслей. Тест обычно приходит в конце цикла, чтобы сохранить дополнительный JMP, и я избегаю подписанных сравнений как из-за слияния макросов, так и для безопасности. Распространенная ошибка заключается в том, чтобы забыть протестировать отрицательные входы. Беззнаковые сравнения не имеют этой проблемы. – icecreamsword

+1

Я бы также добавил, что я пишу свои петли в разных стилях в зависимости от того, что наиболее удобно; от 0 до N-1 (INC/CMP/JB или INC/CMP/JBE); от N до 1 (DEC/JNZ); от N-1 до -1 (INC/JNC); и вариации на выше, указатели, а не индексы. – icecreamsword

+1

@icecreamsword: Забавно, как программирование в asm обычно приводит к выбору подписи и ширины вашей функции args намного внимательнее, чем к C. В C некоторые люди просто делают все, что связано с массивом 'size_t'. –