Вопрос

Вот две очень простые программы.Я бы ожидал получить тот же результат, но я этого не делаю.Я не могу понять, почему.Первые результаты 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
Это было полезно?

Решение

Здесь есть два отдельных вопроса.Первый - это тот факт, что вы получаете разные шестнадцатеричные значения для того, что выглядит как одни и те же операции.Основной факт, которого вам не хватает, заключается в том, что charов повышаются до ints (как есть 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.

Каково решение?Придерживайтесь charы или ints;их смешивание может привести к тому, чего вы не ожидаете.

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

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

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

Другие советы

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

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

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

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

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

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

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

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

Поэтому, когда ты говоришь:

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 в математике целых чисел без знака приводит к ошибке underflow, которая определяется как результат UINT_MAX + 1-5.Если бы это произошло до проведения акции, значение было бы UCHAR_MAX+1-5 (т.е.снова 251).

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

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

Единственная причина, по которой этого не происходит в первой программе, потому что unsigned int и signed 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 определяет преобразования signed в unsigned таким образом, как он это делает - легко реализовать преобразования на архитектурах, которые используют дополнение two для значений со знаком.

Теперь с программой 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 в качестве значения со знаком.Таким образом, независимо от того, интерпретируете ли вы это как вычитание со знаком или без знака, из-за того, что машина использует форму дополнения two, результат соответствует стандарту C без каких-либо дополнительных преобразований.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top