Как в сборке присваивается отрицательное число неподписанной работе 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
может или не может дать ожидаемое эквивалентное значение без знака.