Что (* (QUEUE **) & ((* (q)) [0])) означает в libuv, или Как работает очередь? - PullRequest
0 голосов
/ 11 апреля 2020

Я просто получаю пустые указатели и двойные указатели в C и тому подобное, чтобы попытаться сделать вещи динамичными 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 внутри, совершая какие-то маги c.

1 Ответ

0 голосов
/ 11 апреля 2020

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

  • 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*, потому что, на самом деле, в C невозможно набрать для себя массив указателей.

И QUEUE_INSERT_TAIL btw - это код для объединения двух списков. Интересный аспект этого интерфейса: как вы попадаете на содержание каждого элемента? Взгляните на определение 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 корректируется по адресу struct (здесь user_s), на которую он указывает.

...