Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Что представляет собой данное выражение в Си?

#linux #c


Встретил в коде данную строку:

void *ptr = ((char*)l_p - (unsigned int)&((struct node_s*)0)->list);


Не понимаю, что в данном контексте обозначает & и зачем в данном случае используется
unsigned int. Если возможно, объясните словами, пожалуйста.

Представление самой структуры здесь:

struct node_s{
    char a;
    char b;
    char c;
    char d;
    struct list_head list;
};

    


Ответы

Ответ 1



Перед вами классический хак, применяющийся для реализации так называемой функциональности container_of, позволяющей из указателя на поле известного struct-типа получить указатель на весь объект этого struct-типа. Подвыражение (unsigned int)&((struct node_s*)0)->list это само по себе отдельный хак, использованный для реализации функциональности, аналогичной стандартному макро offsetof. Это подвыражение вычисляет байтовое смещение поля list в struct-типе struct node_s. На данной платформе, очевидно, подвыражение (struct node_s*)0 породит указатель на адрес 0x0 типа (struct node_s *), т.е. указатель на некий воображаемый объект типа struct node_s, располагающийся по адресу 0x0. Соответственно подвыражение ((struct node_s*)0)->list будет полем list этого воображаемого объекта. Благодаря тому, что наш воображаемый объект начинается по адресу 0x0, адрес этого поля &((struct node_s*)0)->list; будет численно равен байтовому смещению поля list в этом воображаемом объекте, т.е. в типе struct node_s. Осталось только привести этот указатель к целочисленному типу (unsigned int)&((struct node_s*)0)->list и мы получим само смещение в байтах (при условии, что оно помещается в unsigned int; здесь существенно более уместным было бы приведение к типу uintptr_t). Так как в процессе этих манипуляций мы ни разу не пытаемся записывать или читать содержимое этого воображаемого объекта, а используем его лишь для вычисления адресов, никаких падений не происходит. Теперь, если l_p является указателем на поле list некоего реального объекта struct node_s, то арифметика ((char*)l_p - (unsigned int)&((struct node_s*)0)->list) даст нам указатель на сам объект struct node_s (правда пока типа char *). Вот ради этого все это и делалось. Еще раз, ваше исходное выражение можно более компактно переписать через стандартное макро offsetof void *ptr = (char*)l_p - offsetof(struct node_s, list); и хаков в нем станет меньше. И, как уже говорилось выше, эту функциональность обычно называют container_of. Ваше исходное выражение - это почти точный аналог void *ptr = container_of(l_p, struct node_s, list); Большинство из произведенных в этом выражении манипуляций (особенно часть, отвечающая за аналог offsetof) являются грубо незаконными в языке С в том смысле, что порождают неопределенное поведение. Т.е., как сказано выше, это хак. Отдельным вопросом является то, был ли этот код написан "руками", или явился результатом расширения стандартного макро (того же offsetof). Для стандартных макро такие трюки позволительны (хотя современные реализации стандартной библиотеки уже отказались от их использования). Для пользовательского кода, без явной необходимости, такие трюки малоприемлемы (но иногда без них не обойтись).

Комментариев нет:

Отправить комментарий