Является ли явное разыменование указателя NULL в C на самом деле арифметикой указателя? - PullRequest
6 голосов
/ 26 декабря 2010

У меня есть этот кусок кода. Похоже, что здесь разыменовывается нулевой указатель, но затем поразрядное И возвращает результат с unsigned int Я действительно не понимаю всей части. Что это должно сделать? Это форма арифметики с указателями?

struct hi  
{
   long a;  
   int b;  
   long c;  
};  

int main()  
{  
    struct hi ob={3,4,5};  
    struct hi *ptr=&ob;  
    int num= (unsigned int) & (((struct hi *)0)->b);  

   printf("%d",num);  
   printf("%d",*(int *)((char *)ptr + (unsigned int) & (((struct hi *)0)->b)));  
}  

Я получаю вывод 44. Но как это работает?

Ответы [ 5 ]

8 голосов
/ 26 декабря 2010

Это не разыменование нулевого указателя. Вы должны посмотреть на весь код. Код говорит: взять число 0, обработать его как struct hi *, выбрать элемент b в структуре, на которую он указывает, и взять адрес этого элемента. Результатом этой операции будет смещение элемента b от начала структуры. Когда вы добавляете его к указателю, вы получаете элемент b, равный 4.

6 голосов
/ 26 декабря 2010

Это дает вам смещение в байтах поля b внутри hi struct

((struct hi *)0) - указатель на hi структуру, начиная с адреса 0.

(((struct hi *)0)->b) - это поле b вышеуказанной структуры.

& (((struct hi *)0)->b) - это адрес вышеуказанного поля.Поскольку структура hi расположена по адресу 0, это смещение b в структуре.

(unsigned int) & (((struct hi *)0)->b) является преобразованием этого значения из типа адреса в unsigned int, поэтомучто его можно использовать как число.

Вы на самом деле не разыменовываете указатель NULL.Вы просто делаете арифметику указателей.


Доступ к (((struct hi *)0)->b) вызовет ошибку сегментации, потому что вы пытаетесь получить доступ к запрещенной ячейке памяти.

Использование & (((struct hi *)0)->b) делаетне дает вам ошибки сегментации, потому что вы берете только адрес этой запрещенной ячейки памяти, но вы не пытаетесь получить доступ к указанному местоположению.

3 голосов
/ 26 декабря 2010

Это не "и", это адрес правого аргумента.
Это стандартный хак для получения смещения члена структуры во время выполнения. Вы приводите 0 к указателю на структуру hi, затем ссылаетесь на член 'b' и получаете его адрес. Затем вы добавляете это смещение к указателю «ptr» и получаете реальный адрес поля «b» структуры, на которую указывает ptr, то есть ob. Затем вы приводите этот указатель обратно к указателю int (потому что b это int) и выводите его. Это второй отпечаток. Первый вывод выводит num, что равно 4 не потому, что значение b равно 4, а потому, что 4 - это смещение поля b в структуре hi. Который является sizeof (int), потому что b следует за a, а a является int ... Надеюсь, это имеет смысл:)

3 голосов
/ 26 декабря 2010

Вы должны использовать 32-битную компиляцию (или 64-битную компиляцию в Windows).

Первое выражение - для num - это общая реализация макроса offsetof из <stddef.h>;оно не переносимо, но часто работает.

Второе выражение добавляет это к 0 (нулевой указатель) и дает тот же ответ - 4. Второе выражение добавляет 4 кбазовый адрес объекта, на который указывает ptr, и это значение в структуре 4.

Ваш вывод не включает перевод строки - это, вероятно, следует (поведение не полностью переносимо, потому что это реализацияопределяется, если вы не включаете новую строку: C99 §7.19.2: «Требует ли последняя строка завершающего символа новой строки, определяется реализацией.»).В Unix-системах это грязно, потому что следующее приглашение появится сразу после 44.

0 голосов
/ 02 января 2011

Просто чтобы уточнить, что вы должны понимать разницу между разыменованием NULL-указателя и тем, когда оно не считается разыменованием. Спецификация фактически диктует, что удаление ссылки не происходит, и фактически оптимизируется, когда в выражении есть оператор & (address-of).

Таким образом, & ((struct T *) 0) -> b) фактически оптимизирует -> и просто перебрасывает это число байтов из смещения 0 и предполагает, что это struct T * Это действительно запутывает вещи для новичков. Тем не менее, он широко используется в ядре Linux - и дает реальное представление о магии list_entry, list_head и различных указателей, которые новички не могут понять.

В любом случае это программный способ найти смещение 'b' в объекте struct T. Он используется в offsetof, а также в других операциях list_head, таких как list_entry.

Для получения дополнительной информации - вы можете прочитать об этом в книге Роберта Лава "Разработка ядра Linux".

...