Адрес и содержимое памяти - PullRequest
0 голосов
/ 06 июля 2018

Я написал

int a;
printf("addr = %p and content = %x\n", (void*)(&a), *(&a));
printf("addr = %p and content = %x\n", (void*)(&a)+1, *(&a)+1);

Что я вижу в выводе

addr = 0x7fffffffde3a and content = 55554810
addr = 0x7fffffffde3b and content = 5555

Я ожидаю увидеть один байт в каждом адресе. Однако я не вижу такой вещи. Почему?

Ответы [ 3 ]

0 голосов
/ 06 июля 2018

Вы использовали директиву форматирования %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 имеют одинаковый размер.

0 голосов
/ 06 июля 2018

Если вы хотите напечатать байты, поиграйте с байтами:

  • используйте unsigned char* или uint8_t* для указателей
  • используйте% hhx, чтобы сообщить printf, что входное значение является длиной символа.

Пример:

int a = 0x12345678;

printf("addr = %p and content = %hhx\n", (void*)&a,     *(uint8_t*)&a);
printf("addr = %p and content = %hhx\n", ((void*)&a)+1, *((uint8_t*)&a+1));

Результат в моем младшем порядке:

addr = 0xbedbacac and content = 78
addr = 0xbedbacad and content = 56

И не забудьте использовать *((uint8_t*)&a+1) вместо *(uint8_t*)&a+1. В этом примере последний вернет 79 (78 + 1).

0 голосов
/ 06 июля 2018

Прежде всего, арифметика указателя и оператор разыменования учитывают тип данных.

Помните, действительна арифметика указателей, которая генерирует указатель, следующий за последним элементом массива, но попытка разыменования сгенерированного указателя является неопределенным поведением.

Попытка разыменования указателя, который указывает на недопустимое расположение в памяти, неопределенное поведение .

Тем не менее,

Цитата C11,

Унарный оператор * обозначает косвенность. [...] Если операнд имеет тип ‘‘ указатель на тип ’’ , результат имеет тип ‘‘ type ’’ .

Итак, в вашем случае *(&a) совпадает с a, который имеет тип int, а спецификатор формата печатает целочисленное значение, хранящееся в a.

Если вы хотите увидеть значение byte-byte * byte , вам нужно привести указатель (адрес a) к char *, а затем разыменовать указатель, чтобы увидеть сохраненное значение в каждом байте.

Итак, (void*)(&a)+1 следует изменить на (char*)(&a)+1, чтобы он указывал на следующий байт памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...