Adding unsigned integers in C

Refresh

December 2018

Views

4.9k time

3

Here are two very simple programs. I would expect to get the same output, but I don't. I can't figure out why. The first outputs 251. The second outputs -5. I can understand why the 251. However, I don't see why the second program gives me a -5.

PROGRAM 1:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b= -5;

c =  (a + b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Output:

c hex: fb
c dec: 251

PROGRAM 2:

#include <stdio.h>

int main()
{

unsigned char  a;
unsigned char  b;
unsigned int  c;

a = 0;
b=  5;

c =  (a - b);

printf("c hex: %x\n", c);
printf("c dec: %d\n",c);

}

Output:

c hex: fffffffb
c dec: -5

5 answers

12

В первой программе, b=-5;присваивает 251 b. (Преобразование к беззнаковому типу всегда уменьшить значение по модулю один плюс максимальное значение типа назначения.)

Во второй программе, b=5;просто присваивает 5 к b, а затем c = (a - b);выполняет вычитание 0-5 в качестве типаint из - за акции по умолчанию - проще говоря, «меньше int» тип всегда повышены до intперед использованием в качестве операндов арифметических и битовых операторов.

Изменить: Одна вещь , которую я пропустил: Так как cесть тип unsigned int, результат -5 во второй программе будут преобразованы , unsigned intкогда назначение cпроизводится, в результате чего UINT_MAX-4. Это то , что вы видите , с помощью %xспецификатора к printf. При печати cс %d, вы получите неопределенное поведение, потому что %dожидает (подпись) intаргумент , и вы сдали unsigned intаргумент со значением , которое не представимо в равнине (подпись) int.

R..
1

То , что вы видите , является результатом как основной аппарат , представляющее числа как стандарт C определяет подписало преобразование без знака типа (для арифметики) и как базовая машина представления чисел (в результате неопределенного поведения в то конец).

Когда я первоначально писал мой ответ я предположил , что стандарт C четко не определяет , как подписанные значения должны быть преобразованы в беззнаковые значения, так как стандарт не определяет , как должны быть представлены подписанные значения или как преобразовать беззнаковые значения подписанных значений когда диапазон находится за пределами , что подписанный типа .

Тем не менее, оказывается, что стандарт не явно определить, что при переходе от отрицательного подписали положительные значения без знака. В случае целое число, отрицательное значение со знаком х будут преобразованы в UINT_MAX + 1-х, так же, как если бы он был сохранен в качестве подписанного значения в дополнительном коде, а затем интерпретируется как значение без знака.

Так что, когда вы говорите:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0; 
b = -5;
c = a + b;

Б значения становится 251, потому что -5 преобразуется в беззнаковый тип значения UCHAR_MAX-5 + 1 (255-5 + 1) с использованием стандарта C. Именно тогда после этого преобразования, что добавление происходит. Это делает A + B такой же, как 0 + 251, который затем хранится в с. Однако, когда вы говорите:

unsigned char  a;
unsigned char  b;
unsigned int c;

a = 0;
b = 5;
c = (a-b);

printf("c dec: %d\n", c);

В этом случае, а и Ь повышены до беззнаковых целых чисел, в соответствии с с, так что они остаются 0 и 5 в цене. Тем не менее 0 - 5 в математике целого числа без знака приводит к сгущенным ошибкам, который определен, чтобы привести к UINT_MAX + 1-5. Если бы это произошло до продвижения, то значение будет UCHAR_MAX + 1-5 (т.е. 251 раз).

Тем не менее, причиной вы видите -5 напечатанную в вашем выводе является сочетанием того, что целое число без знака UINT_MAX-4 и -5 имеют то же самое точное бинарное представление, как -5 и 251 делать с однобайтным типом данными, и тот факт, что, когда вы использовали «% D» в качестве строки форматирования, который сказал Printf интерпретировать значение с как целое число, а не целое число без знака.

Так как преобразование значений без знака для знаковых значений для недопустимых значений не определен, то результат становится конкретной реализации. В вашем случае, так как основная машина использует дополнение до двух для подписанных значений, результат в том, что значение без знака UINT_MAX-4 становится подписанным значением -5.

Единственная причина этого не происходит в первой программе, так как беззнаковый INT и подписанный INT могут оба представляют собой 251, так что преобразование между этими двумя хорошо определена и с помощью «% D» или «% U» не имеет значения. Во второй программе, однако, это приводит к непредсказуемому поведению и становится конкретной реализации, так как ваша стоимость UINT_MAX-4 вышли за пределы диапазона в подписанном междунар.

То, что происходит под капотом

Это всегда хорошо, чтобы перепроверить, что вы думаете, что происходит или что должно произойти с тем, что на самом деле происходит, так что давайте посмотрим на выходе ассемблера от компилятора сейчас, чтобы увидеть то, что происходит. Вот смысл части первой программы:

    mov     BYTE PTR [rbp-1], 0   ; a becomes 0
    mov     BYTE PTR [rbp-2], -5  ; b becomes -5, which as an unsigned char is also 251
    movzx   edx, BYTE PTR [rbp-1] ; promote a by zero-extending to an unsigned int, which is now 0
    movzx   eax, BYTE PTR [rbp-2] ; promote b by zero-extending to an unsigned int which is now 251
    add     eax, edx  ; add a and b, that is, 0 and 251

Обратите внимание, что хотя мы хранить подписанное значение -5 в байтах б, когда компилятор продвигает его, она способствует его на ноле-простирающееся число, а это означает, что это интерпретируются как значение без знака, который представляет собой 11111011 вместо подписанного значения. Тогда продвигаемые значения складываются вместе, чтобы стать гр. Это также объясняет, почему стандарт C определяет подписало неподписанных преобразования так, как это делает - это легко осуществить преобразования на архитектурах, которые используют двоичное дополнение для подписанных значений.

Теперь с программой 2:

    mov     BYTE PTR [rbp-1], 0 ; a = 0
    mov     BYTE PTR [rbp-2], 5 ; b = 5
    movzx   edx, BYTE PTR [rbp-1] ; a is promoted to 32-bit integer with value 0
    movzx   eax, BYTE PTR [rbp-2] ; b is promoted to a 32-bit integer with value 5
    mov     ecx, edx 
    sub     ecx, eax ; a - b is now done as 32-bit integers resulting in -5, which is '4294967291' when interpreted as unsigned

Мы видим, что и Ь вновь повышены перед любой арифметикой, так что мы в конечном итоге вычитание двух беззнаковых Интсов, что приводит к UINT_MAX-4 из-за опустошения, который также -5 как знаковое значение. Так ли истолковать вы его как знаковый или беззнаковое вычитание, из-за машины, используя два форму дополнения, результат соответствует стандарту C без каких-либо дополнительных преобразований.

2

Вы используете спецификатор формата %d. Это относится аргумент как десятичные числа ( в основном int).

Вы получаете 251 из первой программы , потому что (unsigned char)-5это 251 затем распечатать его как десятичные цифры. Он получает повышение до 4 байт , а не 1, а эти биты 0, так что число выглядит 0000...251(где 251это двоичная, я просто не преобразовать его).

Вы получаете -5 из второй программы , потому что (unsigned int)-5некоторые большое значение, но отлиты Ань int, это -5. Он получает лечение , как межд из - за того , как вы используете printf.

Используйте спецификатор формата %udдля печати значений без знака десятичного.

-1

Назначение отрицательного числа без знака переменной в основном нарушение правил. То, что вы делаете, это преобразование отрицательного числа в большое положительное число. Вы даже не гарантируется, технически, что преобразование такой же, от одного процессора к другому - на систему комплемента A 1 (если таковые еще существуют) вы получите другое значение, например.

Таким образом, вы получите то, что вы получите. Вы не можете ожидать, подписали алгебру по-прежнему применяется.

2

Есть два отдельных вопроса здесь. Во - первых, тот факт , что вы получаете различные значения шестигранные для того, что выглядит как те же операции. Основополагающий факт , что вам не хватает в том , что chars повышена до intс (как shorts) выполнять арифметические операции. Вот разница:

a = 0  //0x00
b = -5 //0xfb
c = (int)a + (int)b

Здесь aпродолжается до 0x00000000и bпродолжается до 0x000000fb( не знак продлен, потому что это беззнаковое символ). Затем, добавление выполняется, и мы получаем 0x000000fb.

a = 0  //0x00
b = 5  //0x05
c = (int)a - (int)b

Здесь aпродолжается до 0x00000000и bпродолжается до 0x00000005. Затем вычитание выполняется, и мы получаем 0xfffffffb.

Решение? Палка с charS или intS; смешивая их может привести к вещи , которые вы не ожидаете.

Вторая проблема заключается в том , что unsigned intпечатается , как -5, явно знаковое значение. Однако, в строке, вы сказали , printfчтобы напечатать свой второй аргумент, интерпретируется как знаковое междунар (это то, что "%d"означает). Хитрость здесь в том , что printfне знает , что типы переменных , которые вы прошли. Это просто интерпретирует их так , как строка говорит его. Вот пример , где мы говорим , printfчтобы напечатать указатель как межд:

int main()
{
    int a = 0;
    int *p = &a;
    printf("%d\n", p);
}

Когда я запускаю эту программу, я получаю другое значение каждый раз, что является расположение память a, превращали в основание 10. Вы можете заметить , что такого рода вещи вызывает предупреждение. Вы должны прочитать все предупреждения , ваш компилятор дает вам, и только игнорировать их , если вы полностью уверены , что вы делаете , что вы собираетесь.