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