Как в сборке присваивается отрицательное число неподписанной работе int?

Я узнал о 2 Complement и неподписанном и подписанном int. Поэтому я решил проверить свои знания, насколько я знаю, что отрицательное число хранится в 2 Complement так, чтобы сложение и вычитание не имели бы другого алгоритма и схемы были бы простыми.

Теперь, если я пишу

int main()
{
  int a = -1 ;
  unsigned int b = - 1 ;

  printf("%d %u \n %d %u" , a ,a , b, b);
}

Выход будет равен -1 4294967295 -1 4294967295. Теперь я посмотрел на структуру битов и различные вещи, а затем понял, что -1 в 2 дополнениях 11111111 11111111 11111111 11111111, поэтому, когда я интерпретирую его с помощью% d, он дает -1, но когда я интерпретирую использование %u, он рассматривает его как положительное число и поэтому дает 4294967295. Я проверил сборку кода

.LC0:
    .string "%d %u \n %d %u"
main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     DWORD PTR [rbp-4], -1
    mov     DWORD PTR [rbp-8], -1
    mov     esi, DWORD PTR [rbp-8]
    mov     ecx, DWORD PTR [rbp-8]
    mov     edx, DWORD PTR [rbp-4]
    mov     eax, DWORD PTR [rbp-4]
    mov     r8d, esi
    mov     esi, eax
    mov     edi, OFFSET FLAT:.LC0
    mov     eax, 0
    call    printf
    mov     eax, 0
    leave
    ret

Теперь здесь -1 перемещается в регистр как раз в unsigned и signed. То, что я хочу знать, если только имеет смысл переинтерпретация, то почему у нас есть два типа unsigned и signed, это строка printf format %d и %u, что имеет значение?

Далее, что действительно происходит, когда я назначаю отрицательное число целому числу без знака (я узнал, что инициализатор преобразует это значение из int в unsigned int.), но в коде сборки я этого не видел. Так что же происходит на самом деле?

И как машина знает, когда ей нужно делать 2 Complement, а когда нет, видит ли она отрицательный знак и выполняет 2 Complement?

Я прочитал почти каждый вопрос и ответ, вы можете подумать, что этот вопрос дублируется, но я не мог найти удовлетворительного решения.

Ответ 1

Оба подписанных и неподписанных являются кусками памяти, и в соответствии с операциями важно, как они себя ведут.

Это не имеет никакого значения при добавлении или вычитании, потому что из-за 2-дополнения операции точно совпадают.

Это имеет значение, когда мы сравниваем два числа: -1 меньше 0, а 4294967295 - нет.

О конверсии - для одного и того же размера он просто принимает переменный контент и перемещает его в другой - так что 4294967295 становится -1. Для большего размера он сначала подписывается расширенным, а затем содержимое перемещается.

Как машина теперь - согласно инструкции, которую мы используем. У машин есть либо разные инструкции для сравнения signed и unsigned, либо они предоставляют разные флаги (x86 имеет Carry для неподписанного переполнения и Overflow для переполнения подписей).

Кроме того, обратите внимание, что C расслабляется, как хранятся подписанные числа, они не должны быть 2-дополнениями. Но в настоящее время все общие архитектуры хранят подписанные, как это.

Ответ 2

Существует несколько различий между типами подписанных и неподписанных типов:

  • Поведение операторов <, <=, >, >=, /, % и >> различно при работе с подписанными и неподписанными числами.

  • Составители не обязаны вести себя предсказуемо, если любое вычисление на значении знака превышает диапазон его типа. Даже при использовании операторов, которые будут вести себя одинаково со значениями со знаком и без знака во всех определенных случаях, некоторые компиляторы будут вести себя "интересным" способом. Например, компилятор, заданный x+1 > y, может заменить его на x>=y, если x подписан, но не если x не указан.

Как более интересный пример, в системе, где "короткий" - 16 бит, а "int" - 32 бита, компилятор задает функцию:

unsigned mul(unsigned short x, unsigned short y) { return x*y; }

может предположить, что не может возникнуть ситуации, когда продукт будет превышать 2147483647. Например, если он видел, что функция, вызванная как unsigned x = mul(y,65535); и y, была unsigned short, она может опускать код в другом месте, если y больше 37268.

Ответ 3

Кажется, вы, кажется, пропустили факты, которые, во-первых, 0101 = 5 как в знаках, подписанных, так и без знака, а во-вторых, вы присвоили отрицательное число неподписанному int - то, что ваш компилятор может быть достаточно умным для реализации и, поправьте на подписанный int.

Установка unsigned int в -5 должна технически вызывать ошибку, потому что unsigned ints не может хранить значения под 0.

Ответ 4

Вы могли бы понять это лучше, когда пытаетесь присвоить отрицательное значение целому числу без знака большего размера. Компилятор генерирует код сборки для расширения знака при переносе отрицательного значения небольшого размера в целое число без знака большего размера.

см. это сообщение в блоге для объяснения уровня сборки.

Ответ 5

Выбор знакового целочисленного представления оставлен на платформе. Представление применяется как к отрицательным, так и к неотрицательным значениям, например, если 11012 (-5) является двумя дополнениями к 01012 (5), то 01012 (5) также является двумя дополнениями к 11012 (-5).

Платформа может или не может предоставлять отдельные инструкции для операций с целыми числами с подписью и без знака. Например, x86 предоставляет различные команды умножения и деления для подписанных (idiv и imul) и целых чисел без знака (div и mul), но использует то же дополнение (add) и вычитание (sub) инструкции для обоих.

Аналогично, x86 предоставляет единую команду сравнения (cmp) как для целых чисел, так и без знака.

Арифметические операции и операции сравнения будут устанавливать один или несколько флагов регистров состояния (перенос, переполнение, ноль и т.д.). Они могут использоваться по-разному, когда речь идет о словах, которые должны представлять подписанные значения vs. unsigned.

Что касается printf, вы абсолютно правы, что спецификатор преобразования определяет, отображается ли бит-шаблон 0xFFFF как -1 или 4294967295, хотя помните, что если тип аргумента не совпадают с тем, что ожидает спецификатор преобразования, тогда поведение undefined. Использование %u для отображения отрицательного signed int может или не может дать ожидаемое эквивалентное значение без знака.