12,341,234 - 22,222,222 - это -9,880,988 - пока, очень хорошо.
Итак, давайте посмотрим код:
mov ax, 1234d
mov bx, 1234d
И вот первая проблема,Судя по остальной части кода, цель этих двух инструкций состоит в том, чтобы загрузить значение 12341234 в пару 32-битных регистров bx:ax
, но код, написанный выше, загрузит в значение bx:ax
80 872 658.(на самом деле вы можете интерпретировать это bx:ax
как 12,341,234 как свои собственные пользовательские правила, определяющие это value = bx*10000+ax
, но ваш следующий код, выполняющий вычитание, не уважает такое правило. Так что это примечание является просто демонстрацией того, как я получил "80,872,658"является наиболее естественным («родным») для ЦП, но не единственным возможным - для остальной части ответа давайте предположим, что все значения двоично закодированы «родным» способом ЦП, а затем 1234d: 1234d равно интерпретируется как 80 872 658, для любых других пользовательских правил вам придется написать много больше кода, который будет соблюдать эти правила и вычислять основную арифметику в соответствии с ними).
С точки зрения процессора:он выполняет машинный код, который является двоичным потоком байтовых значений (он не знает, как выглядел ваш текст).Чтобы получить этот машинный код из вашего источника, ассемблер преобразует текст исходного кода "mov ax, 1234d" в биты "1011_1000 1101_0010 0000_0100" (три байта, когда отформатирован шестнадцатеричный формат "B8 D2 04" или когда отформатирован десятичный формат "184 210"4 ", но память компьютера использует биты, поэтому двоичное представление является своего рода тем, с чем действительно работает ЦП.
Этот первый байт 184 будет декодировать ЦП как" это инструкция mov ax,<16 bit immediate>
и следующие два байтазакодирует непосредственно "так что значения 210 и 4 представляют значение 1234.
Процессор x86 - это тип машины с прямым порядком байтов, поэтому два байта" 1101_0010 0000_0100 "после чтения из памяти в этомпорядок, объединяются в 16-битное значение 0000_0100_1101_0010 - «младший» означает, что первый байт является наименее значимым, а завершающий байт является наиболее значимым, поэтому они похожи в обратном порядке (4, 210).
Один байт равен 8 битам, поэтому он может вместить 256 комбинаций (2 8 = 256, 2 - это количество позицийsible состояний на один бит: 0 или 1, 8 - количество бит).
Если вы пишете числа «по-человечески», вы поступаете как ..., 8,9,10 ... замечаете васвдруг использовать две цифры для значения 10. Почему?Поскольку вы исчерпали все доступные однозначные цифры, вы удвоили +1 «верхнюю» цифру (которая ранее была скрыта «0»), сбросили «нижнюю» цифру на 0 и начали заново = 10,11, ..., 19, Через 20 ... опять же после исчерпания пространства нижних цифр вы поднимаете верхнюю часть).
Теперь весь байт может хранить 256 комбинаций различных 8-битных комбинаций, поэтому если вы интерпретируете эти битовые комбинации как начинающиеся целые числав 0 один байт может охватывать диапазон 0..255 для целых чисел.Если вы учитываете «байты», вам потребуется только один байт до значения 255, после чего вы попадете в первую «10» ситуацию (исчерпывающий один байт), и вам придется поднять «верхний» байт на +1 и сбрасывает нижний байт в 0.
В математических терминах десятичный способ записи чисел человеком составляет значение v = сумма (цифра k * 10 k ) для k , переходя от 0 вверх к «количеству цифр - 1».Т.е. 1234 = 1 * 10 3 + 2 * 10 2 + 3 * 10 1 + 4 * 10 0 .
Аналогичным образом для байтов, но имеющих 256 «цифр», значение, представленное несколькими байтами, составляет v = сумма (байт k * 256 k ).
Т.е. эти два байта 210 и 4 представляют значение 210 * 256 0 + 4 * 256 1 = 210 * 1 + 4 * 256 = 210 +1024 = 1234 .
или в двоичном виде 0000_0100_1101_0010 кодирует значение 1 * 2 10 + 1 * 2 7 + 1 *2 6 + 1 * 2 4 + 1 * 2 1 = 1024 + 128 + 64 + 16 + 2 = 1234
Это одно и то же значение все время, просто кодируется по-разному, один раз как две 256-значные цифры "4,210", или как четыре десятичных знака 1,2,3,4, или как одиннадцать двоичных цифр 1,0,0 , 1,1,0,1,0,0,1,0 ... то есть то же самое значение, закодированное как число base-256, base-10 и base-2.
Процессор, естественно, работает в base-2, но программисты на ассемблере любят использовать форматирование значений base-16 (шестнадцатеричное), потому что в шестнадцатеричном формате каждая отдельная цифра представляет ровно четыре бита (0 = 0000, 1 = 0001, 2 = 0010 , ..., D = 1101, E = 1110, F = 1111). Приложив немного практики, вы конвертируете в своей голове гекса в двоичный формат определенного значения.
И вот как вы, вероятно, закончили писать эти две строки кода. Возможно, вы видели где-то что-то вроде «У меня есть 32-битное значение 12345678, поэтому я загружу его в два регистра как mov bx,1234
mov ax,5678
» - что работает хорошо, если все эти числа шестнадцатеричные! Потому что тогда «5678» на самом деле представляет собой 16-битное значение (четыре шестнадцатеричные цифры = 4 * 4 = 16 бит), что в точности соответствует размеру ax
, поэтому, как только вы заполните ax
5678h
, оставшаяся часть 1234h
точно так же поступает в "верхний" 16-битный регистр bx
.
Но использование того же визуального разбиения десятичного значения 12341234 не будет работать. Значение 12341234 при форматировании в базе 16 равно 0BC4FF2h
, поэтому с помощью кода:
mov ax,4FF2h ; or ; mov ax,20466 ; or ; mov ax,(12341234 MOD 65536)
mov bx,00BCh ; or ; mov bx,188 ; or ; mov bx,(12341234 DIV 65536)
; if your assembler supports MOD/DIV syntax like that...
; then all above are identical instructions resulting into
; the same binary machine code
вы загружаете пару регистров bx:ax
с двоичным представлением значения 12341234.
т.е. 188 * 65536 + 20466 = 12341234. (65536 = 2 16 , поскольку теперь это значение закодировано в base-65536, так как «одна цифра» является целой word
, в данном случае word
равно 16 биты => 2 16 = 65536 возможных комбинаций.
Так что, как только вы поймете, как целые значения кодируются в компьютерах, и почему 12341234 представлены двумя значениями слов 188,20466, и вы исправите свой инициал bx:ax
и dx:cx
в соответствии с ним (22222222 = 153158Eh = слова 153h, 158Eh = 339, 5518 => т.е. mov cx,5518
mov dx,339
) - вычитание сделает это:
sub ax, cx ; 20466 - 5518 = 14948 (no borrow)
sbb bx, dx ; 188 - 339 - 0(no_borrow) = -151 (borrow=yes)
Теперь эта пара слов -151, 14948 кодирует значение -151 * 65536 + 14948 = -9880988. Разве это не знакомо?
К сожалению следующая часть:
neg ax
neg bx
Не имеет особого смысла ... в то время как пара слов 151, -14948 представляет значение 9880988, в собственном кодировании 32-битного значения только верхнее значение имеет "знак", поэтому 151, - 14948 фактически интерпретируется как 151,50588, и это кодирует значение 9946524 (ничего общего с -9880988).
Для кодирования значения 9880988 вам потребуется пара слов: 150, 50588 и правильный способ (один из возможных правильных способов ) для отмены двоичного значения отдельными компонентами:
not ax
not bx ; first "one's complement" is done
; "neg" is two's complement, so let's fix it one's to two's
add ax,1
adc bx,0
Опять же, требуется более глубокое понимание того, как значения кодируются в компьютере, почему это работает, но давайте быстро по крайней мере рассмотрим пример с этими значениями ... не (-151, 14948) равно (150, 50587), а затем 50587 +1 означает 50588, перенос не произошел, таким образом, 150 + 0 = 150 -> пара слов "150, 50588", то есть значение 9880988, закодированное как два 16-битных слова.