2011-01-06 2 views
5

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

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

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

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

char *d = &ptr->b; 

Но заявление &ptr->b не относится к действующему адресу. Может кто-нибудь объяснить, где я ошибаюсь?

+0

Это как-то похоже на макрос 'offsetof'. – ruslik

ответ

2

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

4

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

&ptr->b 

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

2

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

Может быть полезно подумать об индексировании массива вместо структуры. C определяет ptr[5] как эквивалент *(ptr + 5), что означает, что &(ptr[5]) - это то же самое, что и &(*(ptr + 5)). Теперь легко увидеть, что & и * «отменить» и оставить вас с (ptr + 5), что связано только с приращением указателя, а не с загрузкой из памяти.

C делает это немного облачным, потому что он отличает lvalues ​​от rvalues. То есть выражение, относящееся к памяти, обрабатывается по-разному в левой части выражения, чем справа. Учитывая заявление, подобное x = y;, компилятор C загрузит значение с адреса y и сохранит его по адресу x. Это различие: y неявно разыменовывается, но x нет.

5

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

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

Смежные вопросы