проверьте, установлен ли флаг carry

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

4 ответов


С условными переходами jc (прыгать, если нести) или jnc (прыжок, если не носить).

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

;; Intel syntax
mov eax, 0
adc eax, 0 ; add with carry

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

вы должны знать, что допустимо проверять флаг переноса, только если он был установлен арифметикой, выполненной внутри встроенного блока asm. Вы не можете проверить carry of a вычисление, которое было выполнено в коде C, потому что есть всевозможные способы компилятора оптимизировать/переупорядочить вещи, которые будут бить флаг переноса.


однако ассемблер x86 HES выделен быстро инструкции по тестированию флага 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, чтобы исчезнуть сомнение в сроке быстро

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.

EDIT: добавлен второй тест, результат которого хранится в регистре Al comulative в регистре 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%.


первая функция выполняет добавление без знака, а затем проверяет переполнение с помощью флага переноса (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 , %[xc]  ;"
     "1:              ;"
     : [xc] "=m" (no_carry)
     );

    if(r)
        *r = result;

    return no_carry;
}

следующая функция предназначена для подписанных ints. Такое же использование volatile применяется. Обратите внимание, что signed integer math прыгает на флаг через 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 , %[xo]  ;"
     "1:              ;"
     : [xo] "=m" (no_overflow)
     );

    if(r)
        *r = result;

    return no_overflow;
}

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

int r, a, b;
...

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

...

встроенная сборка GCC доступна в GCC 3.1 и выше. См.инструкции ассемблера с операндами выражения C, или поиск ' GCC Extended Ассамблея'.

наконец, то же самое в 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 нет встроенного кода, поэтому вам придется закодировать его в сборке (в отдельном файле) и использовать для компиляции use MASM64.