Printf функция форматирования - PullRequest
3 голосов
/ 03 июня 2019

Имея следующий простой код C ++:

#include <stdio.h>

int main() {
    char c1 = 130;
    unsigned char c2 = 130;

    printf("1: %+u\n", c1);
    printf("2: %+u\n", c2);
    printf("3: %+d\n", c1);
    printf("4: %+d\n", c2);
    ...
    return 0;
}

вывод выглядит так:

1: 4294967170
2: 130
3: -126
4: +130

Может кто-нибудь объяснить, пожалуйста, результаты строк 1 и 3?

Я использую компилятор Linux gcc со всеми настройками по умолчанию.

Ответы [ 3 ]

1 голос
/ 03 июня 2019

(Этот ответ предполагает, что на вашем компьютере char находится в диапазоне от -128 до 127, что unsigned char находится в диапазоне от 0 до 255, и что unsigned int находится в диапазоне от 0 до 4294967295, что имеет место.)

char c1 = 130;

Здесь 130 выходит за пределы диапазона чисел, представляемых char.Значение c1 определяется реализацией.В вашем случае число «оборачивается», инициализируя c1 в static_cast<char>(-126).

В

printf("1: %+u\n", c1);

c1 повышается до int, в результате чего-126.Затем он интерпретируется спецификатором %u как unsigned int.Это неопределенное поведение.На этот раз полученное число оказывается уникальным числом, представляемым unsigned int, которое соответствует -126 по модулю 4294967296, что составляет 4294967170.

In

printf("3: %+d\n", c1);

Значение int-126 интерпретируется спецификатором %d как int напрямую и выводит -126 как ожидается (?).

1 голос
/ 03 июня 2019

В случаях 1, 2 спецификатор формата не соответствует типу аргумента, поэтому поведение программы не определено (в большинстве систем). В большинстве систем char и unsigned char меньше, чем int, поэтому они передаются в int, когда передаются как переменные аргументы. int не соответствует спецификатору формата %u, для которого требуется unsigned int.

В экзотических системах (которые не являются вашей целью), где unsigned char равен int, вместо этого он будет повышен до unsigned int, в этом случае 4 будет иметь UB, поскольку для него требуется int.


Объяснение для 3 во многом зависит от реализации указанных деталей. Результат зависит от того, подписан ли char или нет, и от представимого диапазона.

Если бы 130 было представимым значением char, например, когда это тип без знака, тогда 130 было бы правильным выводом. Похоже, что это не так, поэтому мы можем предположить, что char является типом со знаком в целевой системе.

Инициализация целого числа со знаком с непредставимым значением (например, char с 130 в данном случае) приводит к определенному значению реализации.

В системах с представлением дополнения 2 для чисел со знаком - что является вездесущим представлением в наши дни - значение, определяемое реализацией, обычно представляет собой представимое значение, которое совпадает с непредставимым значением по модулю количества представляемых значений. -126 совпадает со 130 по модулю 256 и представляет собой представимое значение char.

1 голос
/ 03 июня 2019

A char - 8 бит. Это означает, что он может представлять 2 ^ 8 = 256 уникальных значений. uchar представляет от 0 до 255, а подписанный char представляет от -128 до 127 (может представлять абсолютно все, но это типичная реализация платформы). Таким образом, присвоение 130 для char выходит за пределы диапазона на 2, и значение переполняется и переносит значение в -126, когда оно интерпретируется как подписанное char. Компилятор видит 130 как целое число и делает неявное преобразование из int в char. На большинстве платформ int 32-битный, а знаковый бит - это MSB, значение 130 легко помещается в первые 8 бит, но затем компилятор хочет выделить 24 бита, чтобы сжать его в символ. Когда это происходит, и вы сказали компилятору, что хотите подписанный символ, MSB первых 8 битов фактически представляет -128. Ой! Теперь у вас есть это в памяти 1000 0010, которое при интерпретации как подписанный символ равно -128 + 2. Мой носитель на моей платформе кричит об этом. ,

angry linter

Я подчеркиваю это важное замечание об интерпретации, поскольку в памяти оба значения идентичны. Вы можете подтвердить это, приведя значение в операторы printf, то есть printf("3: %+d\n", (unsigned char)c1);, и вы снова увидите 130.

Причина, по которой вы видите большое значение в своем первом операторе printf, заключается в том, что вы преобразуете char со знаком в неподписанное int, где char уже переполнено. Машина сначала интерпретирует char как -126, а затем переводит в беззнаковое int, которое не может представлять это отрицательное значение, поэтому вы получаете максимальное значение со знаком int и вычитаете 126.

2 ^ 32-126 = 4294967170. , лото

В операторе 2 printf все, что нужно сделать машине, это добавить 24 ноля для достижения 32-разрядного значения, а затем интерпретировать значение как int. В первом утверждении вы сказали, что у вас есть знаковое значение, поэтому сначала оно превращается в 32-разрядное значение -126, а затем интерпретирует это целое -ve как целое число без знака. Снова, это переворачивает, как это интерпретирует самый важный бит Есть 2 шага:

  1. Подпись char повышается до подписи int, потому что вы хотите работать с int. Символ (вероятно, скопирован и) имеет 24 добавленных бита. Поскольку мы смотрим на значение со знаком, некоторые машинные инструкции будут выполнять дополнение к двум, поэтому память здесь выглядит совсем иначе.
  2. Новая подписанная int-память интерпретируется как unsigned, поэтому машина смотрит на MSB и интерпретирует его как 2 ^ 32 вместо -2 ^ 31, как это произошло в повышении.

Интересная мелочь: вы можете подавить предупреждение clang-tidy linter, если вы сделаете char c1 = 130u;, но вы все равно получите тот же мусор на основе вышеуказанной логики (то есть неявное преобразование отбрасывает первые 24 бита и знаковый бит все равно был равен нулю). Я отправил отчет об отсутствующей функциональности LLVM clang-tidy, основанный на изучении этого вопроса (проблема 42137 , если вы действительно хотите следовать ему) ?.

...