Я начинающий программист на C. Я определил две переменные int x = 0xffffffff
и char y = x
, после чего распечатал их с помощью функции printf("%x\n", y)
; Я ожидал на терминале чего-то вроде ff
, но вместо этого на экране появилось ffffffff
. Мне интересно, как char
может хранить 4 байта памяти, если размер хранилища символа равен 1.
#include <stdio.h>
int main(void) {
int x = 0xffffffff;
char y = x;
printf("%x\n", y);
return 0;
}
Это потому, что %x
предназначен для печати int
. Таким образом, он берет ваш char
, который подписан в вашем случае и является отрицательным, и расширяет его знак до int
того же значения (-1
).
Проще говоря, 0xffffffff
— это представление значения -1
для типа int
. Для типа char
-1
обозначается 0xff
. В обоих типах -1
является представимым, поэтому при его назначении туда и обратно данные не теряются. Потеря произойдет, если значение не представимо ни в одном из типов (подумайте о диапазоне типа).
используйте это printf("%hhx\n", y);
@emre, это работает! Кстати, что означает спецификатор формата %hhx
? Спасибо за ваше предложение.
h
в %hx
означает сокращение или половину unsigned int
. повторение как %hhx
означает еще короче, следовательно, unsigned char
. Значение преобразуется внутри с помощью printf
в (unsigned short)
или (unsigned char)
соответственно перед преобразованием строки. Однако обратите внимание, что стандарт C поддерживает архитектуры, в которых 2 или даже все 3 типа char
, short
и int
имеют одинаковый размер.
... и даже в этом случае значение char
все равно повышается до int
при передаче. Даже если формат %c
, это означает, что вы можете передать int
в %c
.
Если вы измените строку int x = 0xffffffff;
на int x = 0xeeeeeeee;
, то увидите, что данные действительно теряются. См. эту ссылку для демонстрации.
int x = 0xffffffff;
— это поведение, определяемое реализацией, поскольку 0xFFFFFFFF
больше, чем 2^31-1
в системе, где int
— 32-битная система. Размер char
и int
определяется реализацией.
Его реализация определяется, если char
равно signed
или unsigned
:
Если char
равно signed
, то char y = x
присваивает значение -1
x
. При вызове printf()
char
преобразуется в int
, размер которого реализован. В системе, где int
составляет 32 бита, это кодируется как дополнение до двух 0xFFFFFFFF
(начиная с C 2023; в предыдущих версиях это было определено), которое печатается ffffffff
.
Если char
равен unsigned
, то он будет печататься ff
на платформах, где char
равен 8 битам.
Спецификатор формата %x
ожидал unsigned int
, поэтому вам действительно следует привести его как таковое перед печатью для четко определенного поведения. В любом случае %x
не будет генерировать префикс 0x
и выводить строчные буквы.
Вы также можете упомянуть поведение, определяемое реализацией в int x = 0xffffffff;
в 32-битных системах. Кроме того, выходные данные не будут иметь префикса 0x
и использовать заглавные буквы для шестнадцатеричных цифр f
. Для полноты картины код также может работать на платформах, где char
не имеет знака и имеет 32 бита, и будет выводить ffffffff
, и определенно так и должно быть, если тип int
имеет 32 бита значения или более.
@chqrlie Отредактировано, как было предложено (пожалуйста, сообщите мне еще раз, если я что-то пропустил). Если я это сделаю, gcc выдаст мне результат unsigned char y = x
. У вас есть рефери, который может сказать обратное?
случай, о котором я говорил, довольно крайний: количество битов в unsigned char
определяется реализацией. Оно должно быть не менее 8, а на большинстве современных платформ оно равно 8, поэтому, если char
без знака, на этих платформах вывод будет ff
, но на некоторых DSP с более крупными типами char
вывод может быть ffff
или даже ffffffff
.
@chqrlie Спасибо. Уточнил и эту часть. Оставьте комментарий, поскольку это полезный контекст, но полная информация запутывает ответ.
На самом деле здесь происходит довольно много всего. Первый:
int x = 0xffffffff;
Целочисленная константа 0xffffffff
имеет значение 4 294 967 295 (т. е. 232-1) и тип unsigned int
, при условии, что int
32-битное.
Это значение unsigned int
затем преобразуется в int
для инициализации. Он подвергается преобразованию, определяемому реализацией, которое в большинстве случаев приводит к присвоению значения -1. Это значение имеет то же представление, что и исходное значение в дополнении до двух.
Затем в этой строке:
char y = x;
Мы инициализируем y
значением -1. Это значение находится в диапазоне char
(при условии, что char
подписано), поэтому в этом случае при преобразовании из int
в char
изменение значения не происходит. Предполагая представление дополнения до двух, это обозначается как 0xff
.
Затем, когда вы печатаете:
printf("%x\n", y);
Поскольку printf
является переменной функцией, аргумент y
, имеющий тип char
, преобразуется в тип int
перед передачей в функцию. Как и прежде, оба типа могут хранить значение -1, поэтому значение не меняется.
Спецификатор формата %x
ожидает аргумент типа unsigned int
. Поскольку вместо int
был передан unsigned int
, это технически неопределенное поведение, однако маловероятно, что несоответствие знака/беззнака вызовет проблему в какой-либо реальной реализации.
В этом случае значение int
-1 имеет представление 0xffffffff
, которое при интерпретации как unsigned int
будет иметь это значение, поэтому ffffffff
- это то, что печатается.
Итак, чтобы ответить на ваш вопрос:
как char может хранить 4 байта памяти, если размер хранилища char равен 1.
Это не так. Это просто так кажется из-за преобразований и представлений соответствующих типов.
Предполагая представление дополнения до двух, это обозначается как 0xff
. Боюсь, эта часть все еще немного сбивает с толку: если char
подписано, значение равно -1
, которое в системах, использующих представление с дополнением до двух, хранится в байте со всеми установленными битами. Отладчик покажет значение байта FF
, но printf
выведет ffffffff
из-за правила целочисленного продвижения, как описано.
Если вы передаете переменную меньшего размера, чем
int
, в вариативную функцию, напримерprintf
, она автоматически повышается доint
. Функция даже не знает, что вы передалиchar
.