Доступ к элементам структуры с помощью указателей - PullRequest
5 голосов
/ 06 января 2011

Я удивился, когда следующая программа не вылетела.

typedef struct _x {
  int a;
  char b;
  int c;
} x;

main() {
  x *ptr = 0;
  char *d = &ptr->b;
}

Насколько я понимаю, оператор -> имеет более высокий приоритет над оператором &.Поэтому я ожидал, что программа завершится сбоем в приведенном ниже операторе, когда мы попытаемся разыменовать нулевой указатель tr.

char *d = &ptr->b; 

Но оператор &ptr->b оценивается как действительный адрес.Может кто-нибудь объяснить, где я не прав?

Ответы [ 4 ]

5 голосов
/ 06 января 2011

Ваши ожидания были необоснованными. Программы на C не обязательно "сбой", когда вы разыменовываете нулевые указатели. Программы на С демонстрируют так называемое неопределенное поведение , когда вы пытаетесь сделать что-то подобное. Неопределенное поведение может проявляться по-разному. Это может привести к сбою. Или это может произвести что-то, что даже напоминает «рабочую» программу. Последнее, по-видимому, произошло в вашем случае.

Но в любом случае поведение вашей программы не определено. И нет, он не дает «действительный адрес», как вы, кажется, ошибочно полагаете. Числовой адрес, соответствующий месту в памяти, где нет объектов, недопустим (за исключением, конечно, значения нулевого указателя).

4 голосов
/ 06 января 2011

Причина, по которой ваш код не падает, заключается в том, что вы фактически не разыменовали указатель.Обратите внимание, что выражение

&ptr->b

фактически не пытается загрузить содержимое ptr или ptr->b.Вместо этого он просто хранит адрес, где он находится в памяти.В итоге вы получите указатель на то, где должно быть поле b объекта, на который указывает ptr.Это будет несколько байтов после адреса 0, поэтому разыменование только что созданного вами указателя приведет к segfault.

2 голосов
/ 06 января 2011

Для вычисления адреса не требуется доступ к памяти.&ptr->b означает «дай мне адрес поля b структуры, на которую указывает ptr».Для этого не требуется смотреть на то, что может храниться в этой ячейке памяти.

Может быть полезно подумать об индексации массива, а не структуры.C определяет ptr[5] как эквивалент *(ptr + 5), что означает, что &(ptr[5]) совпадает с &(*(ptr + 5)).Теперь легко видеть, что & и * «отменяют» и оставляют вас с (ptr + 5), который включает только увеличение указателя, а не загрузку из памяти.немного облачно, потому что это отличает lvalues ​​от rvalues.Таким образом, выражение, которое относится к памяти, обрабатывается по-разному в левой части выражения, чем в правой части.Учитывая оператор, подобный x = y;, компилятор C загрузит значение с адреса y и сохранит его по адресу x.Это различие: y неявно разыменовывается, а x - нет.

2 голосов
/ 06 января 2011

&ptr->b == sizeof(int), что означает смещение b в пределах _x после _x.a (тип int) относительно адреса *((x*)0).Смещение 4 (типичное для 32-битной архитектуры) сохраняется в указателе d.Вы должны получить доступ к d, чтобы получить ошибку сегмента.

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