Вы использовали директиву форматирования %x
. %x
ожидает unsigned int
. Тип того, что вы передаете в качестве соответствующего аргумента printf
, не меняет этого. printf
будет читать unsigned int
значения из его аргументов, независимо от того, что вы передаете ему Если то, что вы передаете, несовместимо с тем, что читает printf
, поведение вашей программы не определено.
Вы случайно прошли int
. int
не является unsigned int
, но он достаточно близок, так что когда вы читаете int
, ожидая unsigned int
, значение остается прежним, если целое число положительное или равно нулю. Отрицательные целые числа сопоставляются с большим положительным значением (UINT_MAX - a
на машинах с использованием представления дополнения до двух, то есть почти на всех машинах).
Если вы передадите char
(со знаком или нет) в printf
, когда ожидается, что int
(со знаком или нет), поведение четко определено из-за другой функции языка C, которая называется повышением , Значения целочисленных типов, которые меньше int
(т. Е. char
и short
), преобразуются в int
¹. Таким образом, следующий фрагмент программы корректно определен и печатает значение байта по адресу p
(при условии, что …
заменено допустимым значением указателя):
unsigned char p = …;
printf("addr = %p and content = %x\n", (void*)p, *p);
*(&a)
- это то же самое, что и a
. Чтобы увидеть только один байт a
, вы можете привести указатель &a
к типу unsigned char *
.
printf("addr = %p and content = %x\n", (void*)&a, *(unsigned char *)(&a));
Это напечатает один байт представления a
в памяти. Обратите внимание, что представление зависит от порядка машин .
Ваш фрагмент кода не инициализирует a
, поэтому первый вызов printf
печатает любой мусор, который в данный момент находится в месте a
в памяти. Это предполагает, что int
не имеет никакого представления ловушек , что имеет место практически во всех реализациях языка Си.
Второй вызов printf
пытается напечатать *(&a)+1
. Это всего лишь a+1
. Вывод, который вы получаете, удивителен: вы уверены, что не запускали программу с *(&a+1)
? Кажется, это то, что вы хотели исследовать. С *(&a+1)
поведение вашей программы будет неопределенным, потому что это выглядит one int
мимо a
, а a
не находится в массиве из двух или более int
s. На практике вы, скорее всего, получите в стеке то, что было чуть ниже a
, но на это вы не можете рассчитывать.
Если вы хотите увидеть значение байта по адресу, который находится на 1 после начала a
в памяти, вам нужно сначала привести указатель к байтовому указателю. Когда вы добавляете целое число n к указателю, это не добавляет n к адресу , хранящемуся в указателе, оно добавляет n к сам указатель . Это полезно только тогда, когда указатель указывает на значение внутри массива; затем p + n
указывает на n
элементов массива после p
. Фактически, p[n]
эквивалентно *(p+n)
. Если вы хотите добавить 1 к адресу, то вам нужно получить байтовый указатель, то есть указатель на unsigned char
. Контраст:
int a[2] = {0x12345678, 0x9abcdef0};
printf("addr = %p and content = %x\n", (unsigned char*)(&a) + 1, *((unsigned char*)(&a) + 1));
printf("addr = %p and content = %x\n", (&a) + 1, *((&a) + 1));
Это хорошо определено (но со значением, определяемым реализацией, поскольку оно зависит от порядкового номера платформы), при условии, что int
состоит по крайней мере из двух байтов (что не является строго обязательным в C, но имеет место везде, кроме в нескольких встроенных системах).
(void*)(&a) + 1
не является стандартным C, поскольку void
не имеет размера, поэтому нет смысла перемещать указатель на элемент void
на один void
дальше. Однако некоторые реализации, такие как GCC, обрабатывают void*
как байтовые указатели, так же как unsigned char *
, поэтому добавление целого числа к void*
добавляет это целое число к адресу, сохраненному в указателе.
¹ Или unsigned int
, если меньший тип не помещается в int
со знаком, например на платформе, где short
и int
имеют одинаковый размер.