Случайное значение неинициализированного указателя на указатель - PullRequest
1 голос
/ 10 июля 2020

Когда я определяю указатель без его инициализации:

int *pi;

он указывает на случайную часть памяти.

Что происходит, когда я определяю указатель на указатель без его инициализации?

int **ppi;

Куда это указывает? Он должен указывать на другой указатель, но я не определил его, поэтому, возможно, он указывает на случайную часть памяти? Если возможно, не могли бы вы показать разницу на примере?

Ответы [ 2 ]

1 голос
/ 10 июля 2020

Любая локальная переменная, которая не инициализирована, содержит неопределенное значение. Независимо от типа. Здесь нет очевидной разницы между, например, неинициализированными int, int* или int**.

Однако в C есть правило, согласно которому, если вы не получаете доступ к адресу такого неинициализированная локальная переменная, но используя ее значение, вы вызываете неопределенное поведение - что означает ошибку, возможно, cra sh et c. Обоснование вероятно, что такие переменные могут быть размещены в регистрах и не иметь адресуемой области памяти. Подробнее см. { ссылка }.

Итак, все эти примеры ниже плохие и неправильные, поскольку адреса самих локальных переменных никогда не используются:

{
  int i;
  int* ip;
  int** ipp;
  printf("%d\n, i);          // undefined behavior
  printf("%p\n, (void*)ip);  // undefined behavior
  printf("%p\n, (void*)ipp); // undefined behavior
}

Однако, если вы берете где-нибудь адрес переменной, C будет менее строгим. В таком случае вы получите переменную неопределенное значение , что означает, что она может содержать что угодно, и значение может быть несовместимым, если вы обращаетесь к нему несколько раз. Это может быть «случайный адрес» в случае указателей, но не обязательно.

Неопределенное значение может быть так называемым «представлением ловушки», запрещенной двоичной последовательностью для этого типа. В таких случаях доступ к переменной (чтение или запись) вызывает неопределенное поведение. Это вряд ли произойдет для простого int, если только у вас нет очень экзотической системы c, в которой не используется дополнение 2, потому что в стандартных системах дополнения 2 все комбинации значений int действительны, и нет биты заполнения, отрицательный ноль и т. д. c.

Пример (при условии дополнения до 2):

{
  int i;
  int* ip = &i;
  printf("%d\n", *ip);  // unspecified behavior, might print anything
}

Неопределенное поведение означает, что компилятору не нужно документировать поведение. Вы можете получить любой результат, и он не обязательно должен быть последовательным. Но, по крайней мере, программа не будет взламывать sh и сгорать, как это могло бы произойти в случае неопределенного поведения .

Но представления ловушек, скорее всего, относятся к переменным-указателям. Специфический c CPU может иметь ограниченное адресное пространство или при инициализации низкого уровня MMU может быть настроен на то, чтобы определенные регионы были виртуальными, некоторые регионы содержали только данные или некоторые регионы содержали только код et c. Возможно, такой ЦП генерирует аппаратное исключение, даже когда вы считываете недопустимое значение адреса в индексный регистр. Очень вероятно, что это произойдет, если вы попытаетесь получить доступ к памяти через недопустимый адрес.

Например, MMU может заблокировать неконтролируемый код, который пытается выполнить код из сегмента данных в памяти, или доступ к содержимому памяти кода, как к данным.

1 голос
/ 10 июля 2020

Чтобы было понятно, рассмотрим следующее объявление

T *ptr;

, где T - некоторый спецификатор типа. Если объявленная переменная ptr имеет автоматическую c продолжительность хранения, то указатель не инициализируется ни явно, ни неявно.

Значит, указатель имеет неопределенное значение.

T может быть любым тип. Вы можете определить T как, например,

typedef int T;

или

typedef int *T;

Указатели являются скалярными объектами. Таким образом, в этом объявлении

typedef int *T;
T *ptr;

указатель ptr имеет неопределенное значение так же, как и в объявлении

typedef int T;
T *ptr;
...