Печать шестнадцатеричных символов в C

104

Я пытаюсь прочитать строку символов, а затем распечатать шестнадцатеричный эквивалент символов.

Например, если у меня есть строка "0xc0 0xc0 abc123", в которой первые 2 символа находятся c0в шестнадцатеричном abc123формате, а остальные символы - в ASCII, тогда я должен получить

c0 c0 61 62 63 31 32 33

Однако printfиспользование %xдает мне

ffffffc0 ffffffc0 61 62 63 31 32 33

Как мне получить желаемый результат без символа "ffffff"? И почему только c0 (и 80) имеют ffffffсимволы, а остальные нет?

Rayne
источник
Строка, которая соответствует вашему массиву байтов, будет ..."\xc0\xc0abc123"
Burito

Ответы:

133

Вы видите, ffffffпотому что charподписано в вашей системе. В C функции vararg, такие как, printfбудут продвигать все целые числа меньше, чем intдо int. Поскольку charэто целое число (в вашем случае 8-битное целое число со intзнаком ), ваши символы продвигаются через расширение знака.

Поскольку c0и 80имеют в начале 1-бит (и являются отрицательными как 8-битное целое число), они расширяются по знаку, в то время как другие в вашем примере нет.

char    int
c0 -> ffffffc0
80 -> ffffff80
61 -> 00000061

Вот решение:

char ch = 0xC0;
printf("%x", ch & 0xff);

Это замаскирует верхние биты и оставит только нижние 8 бит, которые вы хотите.

Мистический
источник
15
Мое решение с использованием unsigned charпреобразования в gcc4.6 для x86-64 на одну инструкцию меньше ...
lvella
1
Может, я смогу помочь. Это (технически) неопределенное поведение, потому что для спецификатора xтребуется тип без знака, но ch повышается до int. Правильный код будет просто отбрасывать ч до знака, или использовать приведение к беззнаковым символам и спецификаторам: hhx.
2501,
1
Если есть printf("%x", 0), ничего не печатается.
Густаво Мейра
Он ничего не печатает, потому что минимальное значение установлено на 0. Чтобы исправить это, попробуйте printf("%.2x", 0);увеличить минимальное количество нарисованных символов до 2. Чтобы установить максимальное значение, добавьте перед. с номером. Например, вы можете принудительно нарисовать только 2 символаprintf("%2.2x", 0);
user2262111
Любая причина , почему printf("%x", ch & 0xff)должна быть лучше , чем просто использовать printf("%02hhX", a)как в @ brutal_lobster - х ответа ?
maxschlepzig
62

Действительно, есть преобразование типа в int. Также вы можете принудительно использовать тип char, используя спецификатор% hhx.

printf("%hhX", a);

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

printf("%02hhX", a);

ISO / IEC 9899: 201x говорит:

7 Модификаторы длины и их значения: hh Указывает, что следующий спецификатор преобразования d, i, o, u, x или X применяется к аргументу signed char или unsigned char (аргумент будет повышен в соответствии с целочисленными предложениями, но перед печатью его значение должно быть преобразовано в знаковый или в беззнаковый символ); или что следующие

brutal_lobster
источник
30

Вы можете создать беззнаковый символ:

unsigned char c = 0xc5;

Печать даст C5и нет ffffffc5.

Только символы больше 127 печатаются с ffffff отрицательным знаком (символ подписан).

Или вы можете использовать charпри печати:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
источник
3
+1 реальный лучший ответ, явная типизация как можно ближе к объявлению данных (но не ближе).
Bob Stein
13

Вероятно, вы сохраняете значение 0xc0 в charпеременной, которая, вероятно, имеет знаковый тип, и ваше значение отрицательное (установлен самый старший бит). Затем при печати он преобразуется в int, и для сохранения семантической эквивалентности компилятор дополняет дополнительные байты значением 0xff, поэтому отрицательный результат intбудет иметь то же числовое значение, что и ваш отрицательный char. Чтобы исправить это, просто укажите unsigned charпри печати:

printf("%x", (unsigned char)variable);
lvella
источник
13

Вы можете использовать, hhчтобы сказать, printfчто аргумент является беззнаковым символом. Используется 0для получения нулевого отступа и 2установки ширины на 2. xили Xдля шестнадцатеричных символов нижнего / верхнего регистра.

uint8_t a = 0x0a;
printf("%02hhX", a); // Prints "0A"
printf("0x%02hhx", a); // Prints "0x0a"

Изменить : если читатели обеспокоены утверждением 2501, что это как-то не «правильные» спецификаторы формата, я предлагаю им прочитать printfссылку еще раз. В частности:

Несмотря на то, что% c ожидает аргумент типа int, можно безопасно передавать char из-за целочисленного продвижения, которое происходит при вызове функции с переменным числом аргументов.

Правильные спецификации преобразования для символьных типов фиксированной ширины (int8_t и т. Д.) Определены в заголовке <cinttypes>(C ++) или <inttypes.h>(C) (хотя PRIdMAX, PRIuMAX и т. Д. Являются синонимами% jd,% ju и т . Д.) .

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

Редактировать 2 : (редакция «когда-признать-ты ошибаешься»):

Если вы прочитаете действующий стандарт C11 на странице 311 (329 PDF), вы обнаружите:

чч: Указывает , что следующее d, i, o, u, x, или Xспецификатор преобразования относится к signed charили unsigned charаргумент (аргумент будет были повышены в соответствии с целыми поощрений, но его значение должно быть преобразовано в signed charили unsigned charперед печатью); или что следующий nспецификатор преобразования применяется к указателю на signed charаргумент.

Тимммм
источник
Спецификаторы не подходят для типа uint8_t. Типы фиксированной ширины используют специальные спецификаторы печати. См .:inttypes.h
2501
Да, но все целые числа varargs неявно повышаются до int.
Тимммм
Это может быть, но поскольку C определен, поведение не определено, если вы не используете правильный спецификатор.
2501
Но% x - правильный спецификатор. ( charи unsigned charповышаются до int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Вам нужно будет использовать спецификаторы PRI только для вещей, которые не подходят для вашей платформы, intнапример unsigned int.
Timmmm
%xправильно для unsigned int, а не int. Типы char и unsigned char повышаются до int. Кроме того, нет гарантии, что uint8_t определен как unsigned char.
2501,
2

Вероятно, вы печатаете из массива символов со знаком. Либо печатайте из массива беззнаковых символов, либо замаскируйте значение с помощью 0xff: например, ar [i] & 0xFF. Значения c0 расширяются по знаку, поскольку установлен старший (знаковый) бит.

Ричард Пеннингтон
источник
-1

Попробуйте что-то вроде этого:

int main()
{
    printf("%x %x %x %x %x %x %x %x\n",
        0xC0, 0xC0, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33);
}

Что производит это:

$ ./foo 
c0 c0 61 62 63 31 32 33
ObscureRobot
источник