В чем разница между «4 - 12» и «4 + (-12)»?

Я пытаюсь сравнить следующие выражения:

1) 
    mov al, 4
    mov bl, 12
    sub al, bl ; CF == 1

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

2) 
    mov al, 4
    mov bl, -12
    add al, bl ; CF == 0

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

Результаты идентичны, но не переносятся флаги. Почему? Вычитание реализуется путем прибавления к значению дополнения до двух.


person Danny    schedule 20.11.2020    source источник
comment
Это рукописная сборка, сгенерированная компилятором, или просто исследование?   -  person 1201ProgramAlarm    schedule 21.11.2020
comment
CF для беззнаковой арифметики. Таким образом, первый случай становится отрицательным, поэтому у вас есть беззнаковое переполнение. Второй случай в unsigned — это просто 4+244, которые не переполняются.   -  person Jester    schedule 21.11.2020
comment
Но центральный процессор не знает, какие значения он обрабатывает со знаком или без знака.   -  person Danny    schedule 21.11.2020
comment
Я использую сетевой ассемблер (nasm)   -  person Danny    schedule 21.11.2020
comment
Правильно. Во втором случае вы загрузили 1111 0100, который процессор интерпретирует как 244 для целей CF. Это ваша работа - консультироваться с CF или OF в зависимости от того, хотите ли вы неподписанный или подписанный.   -  person Jester    schedule 21.11.2020
comment
Да, но эти две операции равны, а CF - нет...   -  person Danny    schedule 21.11.2020
comment
@ Дэнни Они не равны. Числовой результат равен, но перенос - нет. Сделайте вычитание как длинное вычитание, и вы увидите, что оно генерирует перенос. Вычитание не реализовано как дополнение к двоичному дополнению в x86.   -  person fuz    schedule 21.11.2020
comment
Если считать беззнаковым, первый - 4-12, который переполняется. Второй - 4+244, чего нет, и это не то же самое. В подписанном да, они эквивалентны, но тогда вам нужно проконсультироваться с ОФ, и вы обнаружите, что он равен нулю в обоих случаях.   -  person Jester    schedule 21.11.2020
comment
То есть вычитание не реализовано как дополнение к двойке? Как это реализовано? Спасибо   -  person Danny    schedule 21.11.2020
comment
Это дубликат вашего предыдущего вопроса. Флаг переноса инвертируется в флаг заимствования при вычитании на x86. Не инвертируется по сложению.   -  person old_timer    schedule 21.11.2020
comment
4 - 12 будут заимствованы, поэтому флаг выполнения равен 1 (на x86 и некоторых других архитектурах). Некоторые архитектуры не инвертируют флаг переноса на выходе из вычитания, и на них вы увидите тот же вывод флага переноса.   -  person old_timer    schedule 21.11.2020


Ответы (1)


Вычитание не равно сложению дополнения до двух на x86. Чтобы понять, какое значение примет флаг переноса, вы должны вместо этого выполнить длинное вычитание:

    0000 0100
-   0000 1100
-------------
  1 1111 1000

Видите, как в конце остается заимствование? Это заимствование устанавливает флаг переноса (т. е. перенос равен заимствованию).

Некоторые другие архитектуры, такие как ARM, действительно реализуют вычитание как сложение, но они делают это не как сложение дополнения до двух, а скорее как сложение дополнения до единицы и дополнительного переноса. Это важно при вычитании 0.

Например, ваш метод даст 12 − 0:

    0000 1100
+   0000 0000 (- 0000 0000 => + 0000 0000)
-------------
  0 0000 1100

с четким переносом. Но то, что происходит на самом деле,

    0000 1100
+   1111 1111 (- 0000 0000 => +1111 1111 + 1)
+           1
-------------
  1 0000 1100

с переноской. Эта деталь важна, так как в противном случае сравнение с 0 не будет работать корректно. В этой схеме перенос указывается всякий раз, когда нет заимствования (т. е. перенос дополняется заимствованием).

То, как это делает Intel, и то, как это делает ARM, на самом деле всегда дает один и тот же результат, за исключением того, что флаг переноса работает наоборот. Таким образом, всякий раз, когда ARM устанавливала перенос, Intel очищала его, и наоборот.

Оба подхода к семантике вычитания довольно распространены. Подход ARM немного проще в реализации, поскольку он позволяет напрямую использовать сумматор для вычитания, вообще не касаясь переноса. С подходом Intel ввод и вывод должны дополняться при выполнении вычитания, но дополнительные вентили для этого действительно не имеют значения в общей схеме вещей. С другой стороны, подход Intel более интуитивен для программистов, поскольку представление о флаге переноса, а также о том, что заимствование, имеет больше смысла, если вы визуализируете выполняемую операцию как длинное вычитание.

person fuz    schedule 20.11.2020