Инструкция SUBS вычитает одно чиcло из другого и при выполнении устанавливает флаги состояния:
SUBS Xn, Xs, Operand
Из второго операнда вычитается третий и результат помещается в первый операнд. Инструкция SUBS устанавливает флаг нуля Z, если результат равен 0. Флаг знака N устанавливается, если знаковый бит результата равен 1.
Флаг переноса C устанавливается, если результат вычитания положительный, и сбрасывает флаг переноса, если результат операции отрицательный.
Флаг переполнения V устанавливается в следующих ситуациях:
Если из положительного числа вычитается отрицательное, а результат отрицательный
Если из отрицательного числа вычитается положительное, а результат положительный
Рассмотрим пример:
.global _start _start: mov x0, #1 mov x1, #2 subs x0, x0, x1 // x0 = 1 - 2 = -1 или 0xffffffffffffffff mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь из Х0 (1) вычитаем Х1 (2). В результате получим -1 или 0xffffffffffffffff
. На уровне компьютера вычитание предполагает сложение, где второй операнд инвертируется, затем
к инвертированному значению прибавляется 1, то есть A – B = A + (~B + 1)
. В итоге мы получим, что выражение 1-2
или 0000 0001 - 0000 0010
(в двоичной форме) эквивалентно выражению:
0000 0001 - 0000 0010 = 0000 0001 + (~0000 0010) + 1
И в конечном счете получаем:
0000 0001 + 1111 1110 = 1111 1111
Флаги здесь устанавливаются следующим образом:
Z=0. Результат не равен 0, поэтому флаг нуля Z не устанавливается.
N=1. Старший (знаковый) бит результата равен 1, поэтому устанавливается флаг знака N.
C=0. В результате операции нет переноса, поэтому флаг переноса C не устанавливается.
V=0. Переполнения здесь нет - из полодительного числа вычитается положительное, и результат отрицательный, поэтому флаг переполнения V не устанавливается.
Другой пример:
.global _start _start: ldr x0, =0x8000000000000000 ldr x1, =0x8000000000000000 subs x0, x0, x1 // x0 = 0x0 mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь из 0x8000000000000000
вычитаем это же число. То есть получаем:
0x8000000000000000 - 0x8000000000000000 = 0x8000000000000000 + (~0x8000000000000000)+1 (инверсия с прибавлением 1) = 0x8000000000000000 + 0x8000000000000000 = 0x1_0000000000000000
Флаги здесь устанавливаются следующим образом:
Z=1. Результат равен 0, поэтому устанавливается флаг нуля Z.
N=0. Знаковый бит результата равен 0, поэтому флаг знака N не устанавливается.
C=1. В результате операции происходит перенос, поэтому устанавливается флаг переноса C.
V=0. Поскольку от отрицательного отнимается отрицательное число, то переполнение не возникает, флаг переполнения V не устанавливается.
Третий пример:
.global _start _start: mov x0, #0 ldr x1, =0x8000000000000000 subs x0, x0, x1 // x0 = 0 - 0x8000000000000000 = 0x8000000000000000 mov x8, #93 // номер функции Linux для выхода из программы - 93 svc 0 // вызываем функцию и выходим из программы
Здесь из 0 вычитаем 0x8000000000000000
. То есть получаем:
0 - 0x8000000000000000 = 0 + (~0x8000000000000000)+1 (инверсия с прибавлением 1) = 0 + 0x8000000000000000 = 0x8000000000000000
Флаги здесь устанавливаются следующим образом:
Z=0. Результат не равен 0, поэтому флаг нуля Z не устанавливается.
N=1. Знаковый бит результата равен 1, поэтому устанавливается флаг знака N.
C=0. В результате операции нет переноса, поэтому флаг переноса C не устанавливается.
V=1. Когда из положительного числа вычитаем отрицательное, то мы ожидаем получить положительное число. Здесь же результат отрицательный, поэтому устанавливается флаг переполнения V.
Можно обратить внимание и заметить, что для беззнаковых чисел выражение A – B
устанавливает флаг переноса C, то A больше или равно B.
Для чисел со знаком A больше или равно B, если флаги N и V совпадают - одновременно равны 1 или 0.