Асемблерная вставка в С++, почему положительное число вместо отрицательного

Помогите пожалуйста, не могу понять в чём проблема

#include <iostream>
#include <clocale>

short CalcExp(short a, short b, short p)
{
short Ans; // Переменная для присвоения результата
_asm // Начало ассемблеровской вставки
{
    mov dx, a 
    mov bx, b 
    mov cx, p
    
    xor ax, ax// очищаем регистр ax
    //Начало цикла 
    start : cmp dx, bx
    jg m1 // если а>b, то перейти на метку m1

    cmp dx, 0
    jl m2 // если x<0 то перейти на метку m2
    cmp dx, 0
    jg m3 // если x>0 то перейти на метку m3 иначе x=0
    add ax, 54
    jmp start // возвращает в начало цикла

    m2: mov bx, dx
    mov dx, dx
    mov cx, 5
    mul cx
    add dx, 1
    add ax, dx
    mov dx,bx
    mov bx, b
    mov cx, p
    add dx, cx      
    jmp start // возвращает в начало цикла

    m3 : mov bx, dx
    mov dx, dx
    mov cx, 3
    mul cx
    add dx, a
    add ax, dx
    mov dx, bx
    mov bx, b
    mov cx, p
    add dx, cx
    jmp start // конец цыкла

    m1 : mov Ans, ax
}
return Ans; // Возвращение результата
}
int main()
{
    setlocale(LC_CTYPE, "rus"); // вызов функции настройки локали 
    short a; short b; short p; //Объявление переменных
    std::cout << "Введите а, b и p \n";
    std::cin >> a; // присвоение значений с клавиатуры 
    std::cin >> b;
    std::cin >> p;
    std::cout << "Ответ:";
    std::cout << CalcExp(a, b, p);// Вывод на экран результата работы метода.
}

Прога работает, если передать положительные значения, а вот если например передать в а = -2, то почему то в регистр dx передаётся что то типо 65000


Ответы (1 шт):

Автор решения: nick_n_a
  1. Потому что вы используете беззнаковые операции для знаковых чисел. Ассемблерные команды деляться на знаковые беззнаковые и универсальные. mul - беззнаковая. imul - знаковая. Для mul число -2 будет числом 65534, и число 65534 будет числом 65534 (условно, справедливо для отдельных случаев, для общего случая верно), потому что оно игнорирует знаковый бит. Для imul число 65534 будет числом -2, и -2 будет числом -2 потому что знаковый бит оно считает знаком числа, а не старшим разрядом.

  2. short это 16-битное знаковое представление -2 в нём будет -2, а 65534 в нём будет тоже -2. Смотреть двоичное представление знакового числа. Разряд 15 (слагаемое 32768) это знак. 65536 = 65534 - (-2) это связь для 16-битного числа. 65536 - константа для 16-битного числа. unsigned short -2 в нём будет 65534, а 65534 будет тоже 65534. Для -3 эквивалент будет 65536-3 = 65533, и так далее. (Знаковые числа будут в диапазоне -32768 до 32767).

  3. Знаковые числа сделедует передавать в мнемоники поддерживающие знак (если есть такие), например imul idiv. Тогда результат будет верно посчитан. Если перепутаете - ответ будет странным, посчитаным как для положительного числа. Беззнаковые числа следумет применять к беззнаковым (если такие есть), например mul, div. Иначе при числах где установлен последний бит - результат будет посчитан как для отрицательных чисел, но при переводе получатся "странные" числа.

  4. Знаковость в си/с++ отличают модификаторы signed и unsigned (например unsigned short). Обычно все типы по умолчанию signed, это зависит о т опций компилятора. Но если нужен явно signed - то что бы опции не влияли - добавляют модификатор signed. Если нужно беззнаковое число - применяют unsigned. Если кастить unsigned в signed одинакового размера - биты не теряются. Поэтому после каста "большое" (65534) число станет "отрицательным" (-2), а назад "отрицательне" (-2) - "большим" (65534). Если кастятся типы данных разной размерности -может применяться "расширение знака", там чуть сложнее. signed конвертируются (каст) "с сохранением знака", а unsigned - конвертируются без изменений.

  5. Для 16 битного числа -2 и 65534 - это одновременно "эквивалентные" значения, выбор между -2 и 65534 зависит а)от "отображения" (signed или unsigned) б) от того использована будет signed или unsigned операция. Т.е. конвертация может происходить неявно. Если в одном из случаев будет перепутано signed или unsigned, мы увидим "не то число", или "не тот результат" операции. Поэтому эту особенность можно использовать для запутывания кода, при работе с "сырыми байтами" или с ассемблером - это нужно учитывать что бы не получить UB.

Программе не важно знаковое число или беззнаковое, это важно вам, важно не потерять знак, что бы правильно посчитало. Выводите число в правильном представлении, тогда будет понятно что происходит. При операциях которые зависят от знака - выбирайте правильную - знаковую или беззнаковую соответственно. Если где-то не отображается знак (например часто в регистрах не отображается) - переводите число в знаковое с помощью остнастки, если остнастка это не поддерживает - то переводите "вручную", что бы получить правильный результат. Манипулация с знаками в отдельных случаях может быть использована для запутывания кода. Borland c++ (начиная с 3.0) - поддерживает отображение регистров в watch, даже с преобразованием (кастом, например _EAX или _AX или (short)_AX) . VS - можно (short)ax. Не все среды могут показывать регистры.

→ Ссылка