проверить, установлен ли флаг переноса - PullRequest
13 голосов
/ 29 июня 2010

Используя встроенный ассемблер [gcc, intel, c], как проверить, установлен ли флаг переноса после операции?

Ответы [ 4 ]

14 голосов
/ 29 июня 2010

sbb %eax,%eax будет хранить -1 в eax, если установлен флаг переноса, 0 в случае сброса.Нет необходимости предварительно очищать eax до 0;вычитание eax из себя делает это для вас.Этот метод может быть очень мощным, поскольку вы можете использовать результат как битовую маску для изменения результатов вычислений вместо использования условных переходов.

Вы должны знать, что проверять флаг переноса можно только в том случае, еслибыл установлен арифметикой, выполненной ВНУТРЕННЕГО блока asm.Вы не можете проверить перенос вычислений, которые были выполнены в коде C, потому что есть все виды способов, которыми компилятор мог оптимизировать / переупорядочить вещи, которые бы засоряли флаг переноса.

10 голосов
/ 29 июня 2010

С условными прыжками jc (прыжок при переносе) или jnc (прыжок без переноса).

Или вы можете хранить флаг переноса,

;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry
5 голосов
/ 29 июня 2010

Однако ассемблер x86 выделил fast Инструкции по проверке флага ALU с именем SETcc, где cc - требуемый флаг ALU. Так что вы можете написать:

setc    AL                           //will set AL register to 1 or clear to 0 depend on carry flag

or

setc    byte ptr [edx]               //will set memory byte on location edx depend on carry flag

or even

setc    byte ptr [CarryFlagTestByte]  //will set memory variable on location CarryFlagTestByte depend on carry flag

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

EDIT: Добавлен простой тест, сделанный в Delphi, чтобы убрать сомнения по поводу термина fast

procedure TfrmTest.ButtonTestClick(Sender: TObject);
  function GetCPUTimeStamp: int64;
  asm
    rdtsc
  end;
var
 ii, i: int64;
begin
  i := GetCPUTimeStamp;
  asm
    mov   ecx, 1000000
@repeat:
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    mov   al, 0
    adc   al, 0
    loop  @repeat
  end;
  i := GetCPUTimeStamp - i;

  ii := GetCPUTimeStamp;
  asm
    mov   ecx, 1000000
@repeat:
    setc  al
    setc  al
    setc  al
    setc  al
    loop  @repeat
  end;
  ii := GetCPUTimeStamp - ii;
  caption := IntToStr(i) + '  ' +  IntToStr(ii));
end;

Цикл (1M итераций), использующий инструкцию setc более чем в 5 раз быстрее цикла с adc instriuction.

РЕДАКТИРОВАТЬ: Добавлен второй тест, результаты теста которого хранятся в регистре AL, сгруппированные в регистр CL, чтобы быть более реалистичным.

procedure TfrmTestOtlContainers.Button1Click(Sender: TObject);
  function GetCPUTimeStamp: int64;
  asm
    rdtsc
  end;

var
 ii, i: int64;
begin
  i := GetCPUTimeStamp;
  asm
    xor   ecx, ecx
    mov   edx, $AAAAAAAA

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

    shl   edx, 1
    mov   al, 0
    adc   al, 0
    add   cl, al

  end;
  i := GetCPUTimeStamp - i;

  ii := GetCPUTimeStamp;
  asm
    xor   ecx, ecx
    mov   edx, $AAAAAAAA

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

    shl   edx, 1
    setc  al
    add   cl, al

  end;
  ii := GetCPUTimeStamp - ii;
  caption := IntToStr(i) + '  ' +  IntToStr(ii);
end;

Рутинная часть с инструкцией SETcc еще быстрее примерно на 20%.

2 голосов
/ 19 июня 2011

Первая функция выполняет сложение без знака, а затем проверяет переполнение, используя флаг переноса (CF).Летучие должны остаться.В противном случае оптимизатор переставит инструкции, что в значительной степени обеспечит неверный результат.Я видел, как оптимизатор изменил jnc на jae (который также основан на CF).

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_u32(uint32_t a, uint32_t b, uint32_t* r)
{
    volatile int no_carry = 1;
    volatile uint32_t result = a + b;

    asm volatile
    (
     "jnc 1f          ;"
     "movl $0, %[xc]  ;"
     "1:              ;"
     : [xc] "=m" (no_carry)
     );

    if(r)
        *r = result;

    return no_carry;
}

Следующая функция для подписанных целых чисел.То же использование летучих применяется.Обратите внимание, что математическое целое число со знаком переходит на флаг OF через jno.Я видел, как оптимизатор изменил это значение на jnb (которое также основано на OF).

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(int32_t a, int32_t b, int32_t* r)
{   
    volatile int no_overflow = 1;
    volatile int32_t result = a + b;

    asm volatile
    (
     "jno 1f          ;"
     "movl $0, %[xo]  ;"
     "1:              ;"
     : [xo] "=m" (no_overflow)
     );

    if(r)
        *r = result;

    return no_overflow;
}

В целом, вы можете использовать функции следующим образом.В этой же общей картине многие люди, вероятно, будут отвергать дополнительную работу и эстетическую не-красоту до тех пор, пока pwn'd не будет переполнен / перенесен / потерян

int r, a, b;
...

if(!add_i32(a, b, &r))
    abort(); // Integer overflow!!!

...

Встроенная сборка GCC доступна в GCC 3.1 и выше,См. Инструкции ассемблера с операндами выражения C или поиск «Расширенная сборка GCC».

Наконец, то же самое в Visual Studio будет выглядеть следующим образом (небольшая разница в генерации кода), ноСинтаксис намного проще, поскольку MASM позволяет переходить к метке C:

/* Performs r = a + b, returns 1 if the result is safe (no overflow), 0 otherwise */
int add_i32(__int32 a, __int32 b, __int32* r)
{   
    volatile int no_overflow = 1;
    volatile __int32 result = a + b;

    __asm
    {
        jno NO_OVERFLOW;
        mov no_overflow, 0;
    NO_OVERFLOW:
    }

    if(r)
        *r = result;

    return no_overflow;
}

С другой стороны, приведенный выше код MASM применим только для сборки x86.Для сборки x64 вставка отсутствует, поэтому вам придется кодировать ее в сборке (в отдельном файле) и использовать MASM64 для компиляции.

...