container_of подробно
от Freddy
https://annimon.com/code/?act=comm&id=4693 Поскольку возникли вопросы, как эта магия работает, разберём макрос подробнее.
Итак, есть задача: зная адрес вложенной структуры child (на рисунке выделен светло-зелёным оттенком, так я обозначаю известный адрес), нужно получить адрес структуры parent (выделено цветом, близким к оранжевому, неизвестный адрес), содержащей child. Можно было бы хранить указать на parent в child, но есть несколько проблем. Для примера были даны простейшие структуры, но в реальных задачах структуры могут содержать много элементов и хуже, если есть полный (или почти полный) граф указателей между ними, тогда можно запросто забыть проинициализировать указатель во вложенной структуре на структуру-контейнер, тем более когда вложенных структур несколько. Чтобы этого избежать, применяется арифметика указателей, и ниже подробнее рассмотрим, что же такого делает container_of. Для простоты возьмём первоначальный пример. Ниже я вновь приведу структуры и полный макрос, чтобы не нужно было переходить по ссылке в "полезные коды", а потом разберём его по кусочкам.
1. Рассмотрим объявление макроса: #define container_of(ptr, type, member)
Здесь параметры: ptr - указатель на вложенную структуру. type - тип структуры контейнера и member - имя вложенной структуры.
Если задана структура
то получить указатель указатель на parent можно:
a) зная адрес (пусть будет (p_left) потомка left:
б) зная адрес (пусть будет (p_right) потомка right:
2. Рассмотрим часть, выделенную цветом: ((type *)((char *)(ptr)-(char *)(&((type *)0)->member))). Здесь мы говорим: "Вычисли мне адрес вложенной структуры, если бы контейнер размещался в памяти, начиная с ячейки номер 0x0". Таким образом мы получаем значение смещения потомка, относительно начала контейнера в байтах. В исходном примере первые четыре байта занимает поле parent.id, а значит, что если бы parent располагался в памяти, начиная с адреса 0x0, то parent.child начиналось бы с адреса 0x4 и было бы смещено относительно начала контейнера на 4 байта.
3. Рассмотрим следующий фрагмент: ((type *)(char *)(ptr)-(char *)(&((type *)0)->member))
Зная на сколько байт смещено вложенное поле, мы можем получить адрес начала контейнера, вычитав из адреса child его смещение. Но поскольку нам нужно вычитать именно количество байт, оба указателя приводятся к char *, потому что тип char занимает ровно один байт. Сравните два примера для char и int:
4. И последнее: получившийся адрес приводится к типу контейнера:
(type *)((char *)(ptr)-(char *)(&((type *)0)->member)
Всё
Вопрос на закуску: можно ли изменить смещение child относительно начала контейнера, не изменяя при этом структуру parent? На всякий случай: считаем, что компиляция производится только под одну платформу, так что вариант "перекомпилировать под платформу, где int занимает 2 байта" не рассматривается.
Итак, есть задача: зная адрес вложенной структуры child (на рисунке выделен светло-зелёным оттенком, так я обозначаю известный адрес), нужно получить адрес структуры parent (выделено цветом, близким к оранжевому, неизвестный адрес), содержащей child. Можно было бы хранить указать на parent в child, но есть несколько проблем. Для примера были даны простейшие структуры, но в реальных задачах структуры могут содержать много элементов и хуже, если есть полный (или почти полный) граф указателей между ними, тогда можно запросто забыть проинициализировать указатель во вложенной структуре на структуру-контейнер, тем более когда вложенных структур несколько. Чтобы этого избежать, применяется арифметика указателей, и ниже подробнее рассмотрим, что же такого делает container_of. Для простоты возьмём первоначальный пример. Ниже я вновь приведу структуры и полный макрос, чтобы не нужно было переходить по ссылке в "полезные коды", а потом разберём его по кусочкам.
- #define container_of(ptr, type, member) \
- ((type *)((char *)(ptr)-(char *)(&((type *)0)->member)))
- struct child {
- int id;
- };
- struct parent {
- int id;
- struct child child;
- };
Здесь параметры: ptr - указатель на вложенную структуру. type - тип структуры контейнера и member - имя вложенной структуры.
Если задана структура
- struct parent {
- int id;
- struct child left;
- struct child right;
- }
a) зная адрес (пусть будет (p_left) потомка left:
- parent = container_of(p_left, struct parent, left);
- parent = container_of(p_right, struct parent, right);
3. Рассмотрим следующий фрагмент: ((type *)(char *)(ptr)-(char *)(&((type *)0)->member))
Зная на сколько байт смещено вложенное поле, мы можем получить адрес начала контейнера, вычитав из адреса child его смещение. Но поскольку нам нужно вычитать именно количество байт, оба указателя приводятся к char *, потому что тип char занимает ровно один байт. Сравните два примера для char и int:
- char *p_char = (char *)0; //0x0
- p_char++; //0x1, потому что char занимает 1 байт
- int *p_int = (int *)0; //0x0
- p_int++; //0x4, потому что int занимает в памяти 4 байта
(type *)((char *)(ptr)-(char *)(&((type *)0)->member)
Всё
Вопрос на закуску: можно ли изменить смещение child относительно начала контейнера, не изменяя при этом структуру parent? На всякий случай: считаем, что компиляция производится только под одну платформу, так что вариант "перекомпилировать под платформу, где int занимает 2 байта" не рассматривается.