проверьте, установлен ли флаг 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.