Вы не можете ничего предположить о том, как GCC реализовал операцию a++
, или даже выполнил вычисления до вашего встроенного asm или перед вызовом функции.
Вы могли бы сделать a
(неиспользуемым) входом для встроенного ассема, но gcc все равно мог бы использовать lea
для копирования и добавления вместо inc
или add
или постоянного распространения после встраивания мог превратить его в mov
-посредственный.
И, конечно, gcc мог бы выполнить некоторые другие вычисления, которые пишут FLAGS прямо перед вашим встроенным ассемблером.
Нет способа сделать a++; asm(...)
безопасным для этого
Стоп, вы на неправильном пути. Если вы настаиваете на использовании asm, вам нужно сделать add
или inc
внутри asm, чтобы вы могли прочитать выходные данные флагов. Если вы заботитесь только о флаге переполнения используйте SETCC, в частности seto %0
, чтобы создать 8-битное выходное значение. Или, лучше, используйте синтаксис вывода флага GCC6, чтобы сообщить компилятору, что логический выходной результат находится в условии OF во FLAGS в конце встроенного asm.
Кроме того, переполнение со знаком в C является неопределенным поведением, поэтому фактическое переполнение в a++
уже является ошибкой. Обычно не проявляется , если вы каким-либо образом обнаружите его после факта, но если вы используете a
в качестве индекса массива или что-то еще, gcc мог расширить его до 64-битного, чтобы избежать повторного расширения знака-расширения .
GCC имеет встроенные функции для добавления с обнаружением переполнения, так как gcc5
Существуют встроенные функции для подписанных / неподписанных add, sub и mul, см. Руководство GCC , которые позволяют избежать UB со знаком переполнения и сообщают о наличии переполнения.
bool __builtin_add_overflow (type1 a, type2 b, type3 *res)
- универсальная версия
bool __builtin_sadd_overflow (int a, int b, int *res)
является подписанной int
версией
bool __builtin_saddll_overflow (long long int a, long long int b, long long int *res)
- подписанная 64-битная long long
версия.
Компилятор будет пытаться использовать аппаратные инструкции для реализации этих встроенных функций, где это возможно, например, условный переход при переполнении после сложения, условный переход при переносе и т. Д.
Существует версия saddl
на тот случай, если вы хотите выполнить операцию любого размера long
на целевой платформе. (Для x86-64 gcc int
всегда 32-битный, long long
всегда 64-битный, но long
зависит от Windows или не Windows. Для платформ, таких как AVR, int
будет 16- бит, и только long
будет 32-битным.)
int checked_add_int(int a, int b, bool *of) {
int result;
*of = __builtin_sadd_overflow(a, b, &result);
return result;
}
компилируется с gcc -O3
для x86-64 System V с этим asm, на Godbolt
checked_add_int:
mov eax, edi
add eax, esi # can't use the normal lea eax, [rdi+rsi]
seto BYTE PTR [rdx]
and BYTE PTR [rdx], 1 # silly compiler, it's already 0/1
ret
ICC19 использует setcc
в целочисленном регистре, а затем сохраняет его, то же различие, что и в мопах, но с худшим размером кода.
После встраивания в вызывающую программу, которая сделала if(of) {}
, он должен просто jo
или jno
вместо фактического использования setcc
для создания целого числа 0/1; в общем, это должно быть эффективно встроено.
Кроме того, начиная с gcc7, есть встроенная функция для запроса переполнения дополнения (после перехода к данному типу) без возврата значения.
#include <stdbool.h>
int overflows(int a, int b) {
bool of = __builtin_add_overflow_p(a, b, (int)0);
return of;
}
компилируется с gcc -O3
для x86-64 System V с этим asm, также на Godbolt
overflows:
xor eax, eax
add edi, esi
seto al
ret
См. Также Обнаружение переполнения со знаком в C / C ++