Добавление целых чисел без знака в C - PullRequest
3 голосов
/ 07 сентября 2011

Вот две очень простые программы.Я ожидаю получить тот же результат, но я не получаю.Я не могу понять, почему.Первые выходы 251. Вторые выходы -5.Я могу понять, почему 251. Однако я не понимаю, почему вторая программа дает мне -5.

ПРОГРАММА 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);

}

Вывод:

c hex: fb
c dec: 251

ПРОГРАММА 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);

}

Выход:

c hex: fffffffb
c dec: -5

Ответы [ 5 ]

12 голосов
/ 07 сентября 2011

В первой программе 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.

2 голосов
/ 07 сентября 2011

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

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

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

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

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

Решение? Палка с char с или int с; смешивание их может вызвать то, чего вы не ожидаете.

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

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

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

2 голосов
/ 07 сентября 2011

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

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

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

он обрабатывается как int.

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

1 голос
/ 07 сентября 2011

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

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

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

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

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

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

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

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

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

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

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

Однако причина, по которой вы видите -5, напечатанную в выходных данных, является комбинацией того факта, что целые числа без знака UINT_MAX-4 и -5 имеют одинаковое точное двоичное представление, как -5 и 251 с однобайтовыми Тип данных и тот факт, что когда вы использовали "% d" в качестве строки форматирования, это указывало printf интерпретировать значение c как целое число со знаком вместо целого числа без знака.

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

Единственная причина, по которой это не происходит в первой программе, потому что как unsigned int, так и unsigned int могут представлять 251, поэтому преобразование между ними хорошо определено, а использование "% d" или "% u" не иметь значение. Во второй программе, однако, это приводит к неопределенному поведению и становится специфичным для реализации, так как ваше значение UINT_MAX-4 вышло за пределы диапазона со знаком int.

Что происходит под капотом

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

    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 в байте b, когда компилятор продвигает его, он продвигает его путем расширения нуля числа, что означает, что оно интерпретируется как значение без знака, которое представляет 11111011 вместо подписанного значение. Затем повышенные значения складываются вместе, чтобы стать c. Именно поэтому стандарт 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

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

0 голосов
/ 07 сентября 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...