Позвольте мне дать вам пример:
struct A
{
int some_data;
int other_data;
};
Теперь давайте сказать, что есть эта функция:
int get_some_data_from_other(int *other)
{
struct A *a = container_of(other, struct A, other_data);
return a->some_data;
}
Как вы можете видеть, мы можем сказать, что это оригинальный struct A
, который содержит данный int *other
, зная, в каком поле структуры указывается, на что указывает указатель. Ключ здесь в том, что мы не делаем имеем ссылку на собственно структуру, а просто указатель на один из ее членов.
Это может показаться абсурдным, но на самом деле полезно в некоторых очень умных конструкциях. Один очень распространенный пример - это то, как ядро создает связанные списки. Предлагаю вам прочитать this post on kernelnewbies.org. Давайте посмотрим, короткий пример этого:
struct whatever
{
/* whatever data */
struct list_head mylist;
};
Так struct whatever
есть некоторые данные, но он также хочет, чтобы действовать в качестве узла внутри связанного списка. То, чему они учат вас в школе, - это иметь другую структуру, содержащую указатели next
/prev
, а также указатель на struct whatever
(или void *
). Таким образом, у вас есть узлы, через которые вы получаете свои данные.
По всем стандартам разработки программного обеспечения это на самом деле хорошо. Но стандарты разработки программного обеспечения не имеют отношения к эффективности w.r.t. См. Why should I have written ZeroMQ in C, not C++ (part II).
Нижняя строка с помощью обычного метода выделяет узел связанного списка отдельно от узла данных, т. Е. Вы удваиваете накладные расходы на распределение памяти, освобождение, фрагментацию и пропуски кэша среди других. То, как это делает ядро Linux, противоположно. Каждый узел данных содержит общий узел связанного списка. Общий узел связанного списка ничего не знает о данных или о том, как они были распределены, и знает только, как подключиться к другим связанным узлам списка.
Итак, давайте более глубокий взгляд:
+-------------------+ +---------------------+ +---------------------+
| | | | | |
| WHATEVER DATA | | WHATEVER DATA 2 | | WHATEVER DATA 3 |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
+-------------------+ +---------------------+ +---------------------+
| |----->| |----->| |
| mylist | | mylist 2 | | mylist 3 |
| |<-----| |<-----| |
+-------------------+ +---------------------+ +---------------------+
Что вы имеете в связанном списке являются указателями внутри struct list_head
, которые указывают на другие struct list_head
с. Обратите внимание, что они не указывают на struct whatever
, но на mylist
внутри эти структуры.
Скажите, что у вас есть узел, struct whatever w
. Вы хотите найти следующий узел. Что ты можешь сделать? Во-первых, вы можете сделать w.mylist.next
, чтобы получить указатель на mylist
следующего узла. Теперь вы должны иметь возможность извлечь фактический struct whatever
, содержащий этот узел. Вот где container_of
используется:
struct whatever w_next = container_of(w.mylist.next, struct whatever, mylist);
Наконец, следует отметить, что ядро Linux имеет макросы для прохождения по всем узлам связанного списка, который чаще всего не то, что вы хотите, так что вы на самом деле не нужно используйте container_of
самостоятельно.
По причине это техническое обслуживание. Если все используют макрос, вы можете легко изменить способ назначения указателя во всем проекте, просто отредактировав макрос. –