Неожиданный результат при использовании макроса container_of (Linux kernel) - PullRequest
1 голос
/ 13 марта 2020

У меня проблема с использованием макроса container_of в ядре Linux. У меня есть следующий код

#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type, member) );})


struct list_head
{
    struct list_head *prev;
    struct list_head *next;
};


struct fox
{
    unsigned long tail_length;
    unsigned long weight;
    unsigned int is_fantastic;

    /*Make this struct a node of the linked list*/
    struct list_head list;
};

Я хочу сделать структуру лисы узлом связанного списка.

int main(void)
{
    struct list_head node_first = {.prev=NULL, .next=NULL};
    struct fox first_f = {.tail_length=3, .weight=4, .is_fantastic=0, .list=node_first};

    struct fox *second_f; 
    second_f = container_of(&node_first, struct fox, list);
    printf("%lu\n", second_f->tail_length);
    return 0;
}

Я ожидал, что увижу 3 в терминале, поскольку second_l указывает на структуру firstf_f, но у меня есть 140250641491552 (какое-то "случайное" значение из памяти, как думаю).

Ответы [ 2 ]

3 голосов
/ 13 марта 2020

Это не так, как offsetof предназначен для использования. Предполагается, что вы должны передать указатель на данные, содержащиеся в структуре, чтобы получить указатель на общую структуру (так что struct list_head**). Вместо этого вы передаете копию указателя на неправильное значение (&node_first отличается от &first_f.list (то, что вы должны передать), поскольку это копия)

Например, как это работает, рассмотрим этот struct list_head объект:

(struct list_head) {   // At 0x1000
    .prev = 0,  // At 0x1000
    .next = 0   // At 0x1004
}

Если бы вы позвонили container_of((struct list_head**) 0x1004, struct list_head, next), вы бы получили (struct list_head*) 0x1000.

Этот пример немного сбивает с толку из-за того же типы, так вот еще один пример:

#include <stddef.h>
#include <stdio.h>

#define container_of(ptr, type, member) ({ \
        const typeof( ((type *)0)->member) *__mptr = (ptr); \
        (type *)( (char *)__mptr - offsetof(type, member) );})

struct holds_values {
    int n;
    char c;
};

struct holds_values* get_container(char* c_ptr) {
    // If this was a pointer to a `c` in `holds_values`
    // e.g., `&object.c`, that `char` is sizeof(int) bytes into
    // the object, so this should be mostly the same as
    // `(struct holds_values*) (c - sizeof(int))`
    return container_of(c_ptr, struct holds_values, c);
}

int main() {
    struct holds_values x = { .n = 5, .c = 'a' };

    char* ptr = &x.c;
    struct holds_values* y = get_container(ptr);
    printf("%d\n%d\n", y == &x, y->n);
}
0 голосов
/ 15 марта 2020

Изменить

second_f = container_of(&node_first, struct fox, list);

на

second_f = container_of(&first_f.list, struct fox, list);

Объяснение :

Это не имеет ничего общего с ядром или Linux, это C и G CC.

Строка

struct fox first_f = {.tail_length=3, .weight=4, .is_fantastic=0, .list=node_first};

копирует содержимое node_first в first_f.list. first_f НЕ является контейнером node_first, он содержит копию node_first.

Если вы хотите, чтобы first_f был контейнером node_first, вам нужно определить:

struct fox first_f = {.tail_length=3, .weight=4, .is_fantastic=0, .list={.prev=NULL, .next=NULL}};
struct list_head *node_first = &first_f.list;

т.е. определить node_first как указатель на член first_f.

Обратите внимание, что вы могли бы определить struct fox, чтобы иметь

struct fox
{
    // ...
    struct list_head *list;
};

вместо

struct fox
{
    // ...
    struct list_head list;
};

, то есть struct fox указывает на list_head, и тогда ваш исходный main() будет работать.

Но это просто неправильный способ использования list_head, list_head предназначен для содержания в других структурах.

...