Что означает (*(QUEUE **) &((*(q))[0])) в libuv, или Как работает очередь?

Я просто изучаю указатели void и двойные указатели в C и тому подобное, чтобы попытаться сделать вещи динамичными. Затем я наткнулся на это, которое выглядит как следует:

typedef void *QUEUE[2];

#define QUEUE_NEXT(q)       (*(QUEUE **) &((*(q))[0]))
#define QUEUE_PREV(q)       (*(QUEUE **) &((*(q))[1]))

Для моих глаз происходит слишком много указателей, скобок и ссылок. Интересно, может ли кто-нибудь объяснить:

  • что делает этот раздел (*(QUEUE **) &((*(q)), и
  • как эта очередь может иметь только два элемента.

Как это работает? В частности, у них есть это:

#define QUEUE_INSERT_TAIL(h, q)                             \
  do {                                                      \
    QUEUE_NEXT(q) = (h);                                    \
    QUEUE_PREV(q) = QUEUE_PREV(h);                          \
    QUEUE_PREV_NEXT(q) = (q);                               \
    QUEUE_PREV(h) = (q);                                    \
  }                                                         \
  while (0)

Как это QUEUE_INSERT_TAIL работает?

Или, например, мне также было бы интересно узнать, что происходит с этим:

#define QUEUE_INIT(q)                                                         \
  do {                                                                        \
    QUEUE_NEXT(q) = (q);                                                      \
    QUEUE_PREV(q) = (q);                                                      \
  }                                                                           \
  while (0)

...

QUEUE_INIT(&loop->wq);
QUEUE_INIT(&loop->idle_handles);
QUEUE_INIT(&loop->async_handles);
QUEUE_INIT(&loop->check_handles);
QUEUE_INIT(&loop->prepare_handles);
QUEUE_INIT(&loop->handle_queue);

В конце концов, все они используют QUEUE_NEXT и QUEUE_PREV внутри, творя какую-то магию.


person Lance Pollard    schedule 11.04.2020    source источник
comment
Взгляните на этот пример, он должен немного помочь. По сути, любая структура в очереди должна содержать элемент QUEUE, который используется для связывания элементов в циклическую двусвязную цепочку. Макрос QUEUE_DATA используется для доступа к исходной структуре из указателя на ее элемент QUEUE.   -  person Tom Karzes    schedule 11.04.2020


Ответы (1)


Это интерфейс к круговому связанному списку. Когда вы разрешите использование первого макроса, вы получите:

  • QUEUE_NEXT(&loop->wq) ->
  • *(QUEUE **) &((*( &loop->wq ))[0]) ->
  • *(QUEUE **) &(( loop->wq )[0]) ->
  • *(QUEUE **) &( loop->wq[0] ) ->
  • *(QUEUE **) &loop->wq[0]

что фактически совпадает с (QUEUE *)( loop->wq[0] ) или просто loop->wq[0]

Это, конечно, работает, только если wq имеет тип QUEUE, который представляет собой не что иное, как массив из двух указателей void. Автор прибегнул к void*, потому что, на самом деле, в C невозможно определить тип массива указателей на себя.

Кстати, QUEUE_INSERT_TAIL — это код для объединения двух списков. Интересным аспектом этого интерфейса является то, как вы получаете доступ к содержимому каждого элемента? Взгляните на определение QUEUE_DATA

#define QUEUE_DATA(ptr, type, field)                                          \
  ((type *) ((char *) (ptr) - ((long) &((type *) 0)->field)))

и это использование

struct user_s {
  int age;
  char* name;

  QUEUE node;
};
user = QUEUE_DATA(q, struct user_s, node);

Это решает

  • QUEUE_DATA(q, struct user_s, node) ->
  • (struct user_s*) ((char *) (q) - ((long) &((struct user_s*) 0)->node))

который фактически возвращает адрес содержащей его структуре путем вычитания смещения ее члена QUEUE, поэтому значение q корректируется по адресу структуры (здесь user_s), на которую он указывает.

person yacc    schedule 11.04.2020