Что происходит в фоновом режиме или в памяти, когда мы приводим char * к int * - PullRequest
3 голосов
/ 08 июля 2019

Я учусь наведению типов указателей и случайно захожу в эту программу

#include <stdio.h>
main() { 
  char* p="01234567890123456789";
  int *pp = (int *)p;              
  printf("%d",pp[0]);
}

При выполнении вышеуказанной программы, вывод 858927408 Что это за случайные числа и откуда они берутся? Что происходит в фоновом режиме или в памяти?

Редактировать: И если я напишу printf("%c",pp[0]);, то вывод будет 0, что правильно, но когда я изменю pp[0] на pp[1], тогда вывод будет 4, но как?

Ответы [ 5 ]

3 голосов
/ 08 июля 2019

Если вы выразите результат в шестнадцатеричном (% x), вы увидите, что:

858927408 = 0x33323130
  • 0x33 является кодом ASCII для '3'
  • 0x32 - это код ASCII для '2'
  • 0x31 - это код ASCII для '1'
  • 0x30 - это код ASCII для '0'

Таким образом, вы просто отображаете память, хранящую 0123456... Но так как ваш процессор little endian , вы видите инвертированные коды.

В памяти у вас есть (в гекса)

30 31 32 33 34 35 36 37 38   # 0 1 2 3 4 5 6 7 8
39 30 31 32 33 34 35 36 37   # 9 0 1 2 3 4 5 6 7
38 39 00                     # 8 9\0  

В printf("%d...") вы читаете 4 первых байта как маленькое целое число с прямым порядком байтов, поэтому он отображает результат 0x33*0x1000000 + 0x32*0x10000 +0x31*0x100 +0x30


С %c все по-другому:

Если вы напишите printf("%c", pp[0]), вы попытаетесь напечатать ОДИН символ из 0x33323130, поэтому 0x30 сохраняется (в вашем случае это может быть UB в некоторых случаях, я не уверен), поэтому он отображает «0», код ascii 0x30

Если вы напишите printf("%c", pp[1]), вы попытаетесь напечатать ОДИН символ из 0x37363534, поэтому значение 0x34 будет сохранено, и на нем отобразится «4», код ascii равен 0x34

2 голосов
/ 08 июля 2019
  1. Если ваша реализация C использует ASCII, то первые четыре байта строки "01234567890123456789" - это 48, 49, 50 и 51 (шестнадцатеричные 0x30, 0x31, 0x32 и 0x33), которые являются кодами ASCII для символов «0», «1», «2» и «3».
  2. (int *)p преобразует p из char * в int *. Преобразование указателя не полностью определено стандартом C. Смотрите примечания ниже. Если нет проблем с выравниванием, в большинстве реализаций C результат этого преобразования будет указывать на то же место, на которое указывает p.
  3. Установив pp в (int *)p, pp[0] извлекает байты в pp и интерпретирует их как int. В вашей реализации int объекты имеют четыре байта, а байты упорядочены по младшему значащему байту в памяти с самым низким адресом. Таким образом, байты 0x30, 0x31, 0x32 и 0x33 считываются из памяти и превращаются в целое число 0x33323130 (десятичное 858927408).

Примечания о преобразовании указателей и псевдонимах

Здесь важны три момента преобразования указателей:

  • Если выравнивание неверное, преобразование указателя не определяется стандартом C. В частности, во многих реализациях C объекты int должны быть выровнены на четыре байта, тогда как объекты char могут иметь любое выравнивание. Если адрес в p неправильно выровнен для int, то выражение (int *)p может вызвать сбой программы или привести к нежелательным результатам.
  • Даже если выравнивание правильное, стандарт C не гарантирует, каков результат преобразования общего char * в int *, за исключением того, что преобразование результата обратно в char * даст исходный указатель (или эквивалентный указатель). Во многих реализациях C это преобразование даст указатель на один и тот же адрес, просто с другим типом.
  • Выражение pp[0] обращается к байтам в p, как если бы они были int. Это нарушает правило в стандарте C, называемое правилом псевдонимов, которое гласит, что объект должен иметь свое значение, доступное только через выражение, использующее правильный тип. Есть некоторые подробности о том, какие типы являются правильными, но int никогда не является правильным типом для char (или для нескольких char). Когда это правило нарушается, стандарт C не определяет поведение.

Последний пункт важен, потому что реализации C могут или не могут поддерживать псевдонимы. Некоторые реализации C поддерживают псевдонимы (то есть они определяют поведение, хотя стандарт C этого не делает), потому что он широко использовался, и они хотят поддерживать существующий код, который его использует, или потому, что он необходим в определенных типах программного обеспечения. Некоторые реализации C не поддерживают псевдонимы, потому что это позволяет им лучше оптимизировать программы. (Если компилятор может предположить, что int * никогда не указывает на float, тогда он может избежать перезагрузки данных float после назначений с помощью указателей int, поскольку эти назначения не могли изменить float данные.) Некоторые компиляторы имеют переключатели, так что вы можете включить или отключить поддержку псевдонимов.

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

char *p = "01234567890123456789";
int i;
memcpy(&i, p, sizeof i);
printf("%d\n", i);
1 голос
/ 08 июля 2019

858927408 при преобразовании в гекс составляет 0x33323130

Как раз в вашей системе у вас есть формат little-endian . В этом формате младший бит целого числа сохраняется первым.

Первые 4 байта строки берутся для целого числа. "0123" Значения ascii 0x30, 0x31, 0x32, 0x33 соответственно. Так как это little-endian. LSByte целого числа 0x30, а MSbyte целого числа 0x33.

Вот как вы получаете 0x33323130 в качестве вывода.

Редактировать Относительно дополнительного вопроса из OP

И если я напишу printf ("% c", pp [0]); тогда вывод равен 0, что правильно, но когда я изменяю pp [0] на pp [1], вывод равен 4, но как?

Если у вас есть %c в printf и вы задаете целочисленный параметр, вы конвертируете целое число в символ, т. Е. Байт LS берется 0x30 и печатается как ASCII.

для pp[1] это следующее целое число в массиве, которое будет через 4 байта. Таким образом, байт LS в этом случае будет 0x34, а 4 будет напечатан после преобразования в ASCII.

1 голос
/ 08 июля 2019

Это результат ((51×256+50)×256+49)×256+48, где 51 - это ASCII-код «3», а 50 - ASCII-код «2» и т. Д.На самом деле, pp[0] указывает на 4 байта памяти (int - 4 байта), и эти 4 байта равны «0123», а int на вашем компьютере имеет младший порядок, поэтому «0» (48 в числовом)LSB и «3» - это MSB.

p[1] - это один байт после p[0], потому что p - это указатель на байтовый массив, но pp[1] - это 4 байта после pp[0], поскольку pp - это указатель на массив int и int4 байта.

0 голосов
/ 08 июля 2019

Он просто устанавливает начальный адрес объекта int в начале строки. Фактическое значение int будет зависеть от порядка байтов и размера (int).

, поскольку "01234567890123456789" - это {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39 ...} в памяти, если порядковый номер мал и sizeof(int) == 4, значение будет 0x0x33323130. Если значение порядка велико, то значение будет 0x30313233

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