Перейти к содержимому

Связные списки и очереди

Добрый день, уважаемый читатель! Сегодня я хочу познакомить вас с одной замечательной библиотекой, “встроенной” в ESP-IDF – библиотекой для работы с однонаправленными или двунаправленными связными списками.

Строго говоря, эта библиотека не является частью ESP-IDF, как и Espressif не является авторами данной библиотеки. Создана она аж в 1993 году в University of California и просто используется в ESP32 в готовом виде. Поэтому справки по её использованию в официальной справочной системе вы не найдете. Сам я наткнулся на эту библиотечку совершенно случайно, когда изучал исходники ESP-IDF, а до этого пользовался самодельной реализацией. Но эта готовая реализация настолько удобна, что необходимости в велосипедах более нет.

Связные списки удобно применять для хранения в памяти любых структурированных данных, когда длина этих данных заранее не известна. Можно, конечно использовать динамические массивы, но в некоторых случаях использование списков более оправданно.

 

Описание библиотеки

Библиотека представляет собой набор макросов препроцессора для быстрого создания списков четырех типов и содержит все необходимые функции для работы с ними:

  • Односвязные списки SLIST
  • Односвязные хвостовые очереди STAILQ
  • Списки (двусвязные) LIST
  • Хвостовые очереди TAILQ

Вы можете выбрать любой тип очереди, исходя из своих целей и задач, а также необходимых для их реализации функций.

Для подключения библиотеки в свой проект добавьте строчку:

#include "sys/queue.h"

А теперь давайте познакомимся со всем этих хозяйством более подробно…

Базовые функции

Все четыре структуры поддерживают следующие функции:

  • _INSERT_HEAD – вставка новой записи в начало списка
  • _INSERT_AFTER – вставка новой записи после любого элемента в списке
  • _NEXT – прямой обход списка
  • _SWAP – замена содержимого двух списков 

Здесь не случайно все _МАКРОСЫ записаны с символом подчеркивания, потому что к каждой функции при использовании необходимо будет добавить тип списка, например для односвязного списка функция добавления в начало списка будет записана как SLIST_INSERT_HEAD(…)

Односвязные структуры

Односвязные типы структур данных являются простейшими и поддерживают указанные выше функции плюс ещё несколько.

SLIST

Односвязный список начинается с одного прямого указателя. Элементы связаны одним указателем для минимального объема памяти и накладных расходов на манипулирование указателем за счет удаления O(n) для произвольных элементов. Новые элементы могут быть добавлены в список после существующего элемента или в начало списка. Элементы, удаляемые из головы списка, должны использовать явный макрос для этой цели для оптимальной эффективности. Односвязный список может быть пройден только в прямом направлении.

Односвязные списки добавляют к базовым следующие функции:

  • SLIST_REMOVE_HEAD – удаление первой записи в списке
  • SLIST_REMOVE_AFTER – удаление записи после любого элемента в списке
  • SLIST_REMOVE 🐌 – O(n) удаление любой записи в списке
  • SLIST_CONCAT 🐌 – O(n) они могут быть объединены.

Односвязные списки идеально подходят для приложений с большими наборами данных и небольшим количеством удалений или без них, а также для реализации очереди LIFO.

STAILQ

Односвязная хвостовая очередь характеризуется парой указателей, один на начало списка, а другой на конец списка. Элементы по прежнему связаны одним указателем для минимального объема памяти и накладных расходов на манипулирование указателем за счет удаления O(n) для произвольных элементов. Новые элементы могут быть добавлены в список после существующего элемента, в начало списка или в конец списка. Элементы, удаляемые из головы хвостовой очереди, должны использовать явный макрос для этой цели для оптимальной эффективности. Односвязная хвостовая очередь может проходить только в прямом направлении.

Односвязные хвостовые очереди добавляют следующую функциональность:

  • STAILQ_INSERT_TAIL – записи могут быть добавлены в конец списка
  • STAILQ_REMOVE_HEAD – удаление первой записи в списке
  • STAILQ_REMOVE_AFTER – удаление записи после любого элемента в списке
  • STAILQ_REMOVE 🐌 – O(n) удаление любой записи в списке
  • STAILQ_CONCAT – они могут быть объединены.

Однако ничто в этом мире не дается просто так, и за новые возможности приходится платить:

  • Все вставки элементов должны указывать заголовок списка.
  • Для каждой записи заголовка списка требуется два указателя, а не один.
  • Размер кода примерно на 15% больше, а операции выполняются примерно на 20% медленнее, чем на односвязных списках.

Односвязные хвостовые очереди идеально подходят для приложений с большими наборами данных и небольшим количеством удалений или без них, а также для реализации очереди FIFO.

 

Двусвязные структуры

Все двусвязные типы структур данных (списки и хвостовые очереди) дополнительно позволяют:

  • _INSERT_BEFORE – вставка новой записи перед любым элементом в списке
  • _REMOVE – быстрое удаление любой записи из списка

Однако:

  • Для каждого элемента требуется два указателя, а не один
  • Размер кода и время выполнения операций (кроме удаления) примерно в два раза больше, чем у односвязных структур данных

LIST

Двусвязный список начинается с одного прямого указателя (или массива прямых указателей для заголовка хеш-таблицы). Элементы связаны между собой двумя указателями, так что произвольный элемент может быть удален без необходимости обхода списка. Новые элементы могут быть добавлены в список до или после существующего элемента или в начале списка. Но добавление новых значений в список занимает больше времени, чем в односвязный. Список может быть пройден в любом направлении.

Списки добавляют следующие функции к вышеперечисленным (базовым и общим для двусвязных типов):

  • LIST_PREV – их можно пройти в обратном направлении
  • LIST_CONCAT 🐌 – O(n) они могут быть объединены

Однако:

  • Для обхода в обратном направлении необходимо указать запись для начала обхода и список, в котором она содержится

TAILQ

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

Хвостовые очереди добавляют следующие функции:

  • TAILQ_INSERT_TAIL – записи могут быть добавлены в конец списка
  • TAILQ_PREV – их можно пройти назад, от хвоста к голове
  • TAILQ_CONCAT – они могут быть объединены

Однако:

  • Все вставки и удаления списка должны указывать заголовок списка
  • Для каждой записи заголовка требуется два указателя, а не один
  • Размер кода примерно на 15% больше, а операции выполняются примерно на 20% медленнее, чем двусвязные списки

 

Перечень реализованных функций для каждого из типов вы можете увидеть в сводной таблице ниже:

Примечание:

  • + означает, что макрос доступен
  •  означает, что макрос недоступен
  • s означает, что макрос доступен, но работает 🐌 медленно (выполняется за время O(n)).

 

Макросы

Теперь давайте рассмотрим эти макросы – функции более подробно.

В определениях макросов TYPE – это имя определяемой пользователем структуры, которая должна содержать поле типа SLIST_ENTRY, STAILQ_ENTRY, LIST_ENTRY или TAILQ_ENTRY с именем NAME.

Аргумент HEADNAME – это имя определяемой пользователем структуры, которая должна быть объявлена ​​с использованием макросов SLIST_HEAD, STAILQ_HEAD, LIST_HEAD или TAILQ_HEAD

Односвязные списки

Односвязный список возглавляет структура, определяемая макросом SLIST_HEAD, которая объявляется следующим образом:

SLIST_HEAD(HEADNAMETYPEhead;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, которые должны быть связаны со списком. Указатель на начало списка позже может быть объявлен как:

struct HEADNAME *headp;

Имена head и headp выбираются программистом самостоятельно.

  • Макрос SLIST_ENTRY объявляет структуру, которая соединяет элементы в списке.
  • Макрос SLIST_HEAD_INITIALIZER оценивается как инициализатор для заголовка списка (например NULL).
  • Макрос SLIST_INIT инициализирует список, на который ссылается head.
  • Макрос SLIST_EMPTY принимает значение true, если в списке нет элементов.
  • Макрос SLIST_FIRST возвращает первый элемент в списке или NULL, если список пуст.
  • Макрос SLIST_NEXT возвращает следующий элемент в списке.
  • Макрос SLIST_INSERT_HEAD вставляет новый элемент elm в начало списка.
  • Макрос SLIST_INSERT_AFTER вставляет новый элемент elm после элемента listelm.
  • Макрос SLIST_REMOVE удаляет элемент elm из списка. Универсальный, но и самый не оптимальный макрос.
  • Макрос SLIST_REMOVE_AFTER удаляет элемент после elm из списка. В отличие от SLIST_REMOVE, этот макрос не проходит по всему списку.
  • Макрос SLIST_REMOVE_HEAD удаляет элемент elm из начала списка. Для оптимальной эффективности элементы, удаляемые из начала списка, должны явно использовать этот макрос вместо универсального макроса SLIST_REMOVE.
  • Макрос SLIST_SWAP меняет местами содержимое head1 и head2.
  • Макрос SLIST_FOREACH просматривает список, на который ссылается head, в прямом направлении, присваивая каждому элементу по очереди var.
  • Макрос SLIST_FOREACH_FROM ведет себя идентично SLIST_FOREACH, когда var имеет значение NULL. В противном случае он обрабатывает var как ранее найденный элемент SLIST и начинает цикл с var вместо первого элемента в SLIST, на который ссылается head. То есть его можно использовать для продолжения просмотра списка.
  • Макрос SLIST_FOREACH_SAFE обходит список, на который ссылается заголовок head, в прямом направлении, присваивая каждый элемент по очереди var. Однако, в отличие от SLIST_FOREACH, здесь разрешено как удалять var, так и безопасно освобождать ее внутри цикла, не мешая обходу.
  • Макрос SLIST_FOREACH_FROM_SAFE ведет себя идентично SLIST_FOREACH_SAFE , когда var имеет значение NULL, в противном случае он обрабатывает var как ранее найденный элемент SLIST и начинает цикл с var вместо первого элемента в SLIST, на который ссылается head.

Односвязная хвостовая очередь

Односвязная хвостовая очередь возглавляется структурой, определяемой макросом STAILQ_HEAD. Эта структура содержит два указателя, один на первый элемент в хвостовой очереди, а другой на последний элемент в хвостовой очереди. Элементы связаны одним указателем для минимальных накладных расходов на манипулирование указателемНовые элементы могут быть добавлены в хвостовую очередь после существующего элемента, в начало хвостовой очереди или в конец хвостовой очереди. Структура STAILQ_HEAD объявляется следующим образом:

STAILQ_HEAD(HEADNAME, TYPEhead;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, которые должны быть связаны с хвостовой очередью. Указатель на голову хвостовой очереди позже может быть объявлен как:

struct HEADNAME *headp;

Имена head и headp выбираются программистом самостоятельно.

Макрос STAILQ_HEAD_INITIALIZER оценивается как инициализатор хвостовой очереди head (например NULL).

Макрос STAILQ_ENTRY объявляет структуру, которая соединяет элементы в хвостовой очереди.

Макрос STAILQ_INIT инициализирует хвостовую очередь, на которую ссылается head.

Макрос STAILQ_EMPTY принимает значение true, если в хвостовой очереди нет элементов.

Макрос STAILQ_FIRST возвращает первый элемент в хвостовой очереди или NULL, если хвостовая очередь пуста.

Макрос STAILQ_LAST возвращает последний элемент в хвостовой очереди. Если хвостовая очередь пуста, возвращаемое значение равно NULL .

Макрос STAILQ_NEXT возвращает следующий элемент в хвостовой очереди или NULL, этот элемент является последним.

Макрос STAILQ_INSERT_HEAD вставляет новый элемент elm в начало хвостовой очереди.

Макрос STAILQ_INSERT_TAIL вставляет новый элемент elm в конец хвостовой очереди.

Макрос STAILQ_INSERT_AFTER вставляет новый элемент elm после элемента listelm.

Макрос STAILQ_REMOVE удаляет элемент elm из хвостовой очереди.

Макрос STAILQ_REMOVE_HEAD удаляет элемент в начале хвостовой очереди. Для оптимальной эффективности элементы, удаляемые из головы хвостовой очереди, должны явно использовать этот макрос, а не общий макрос STAILQ_REMOVE.

Макрос STAILQ_FOREACH проходит хвостовую очередь, на которую ссылается head, в прямом направлении, присваивая каждому элементу по очереди var.

Макрос STAILQ_FOREACH_SAFE проходит хвостовую очередь, на которую ссылается head, в прямом направлении, присваивая каждый элемент по очереди var. Однако, в отличие от STAILQ_FOREACH() здесь разрешено как удалить var, так и безопасно освободить его из цикла, не мешая обходу.

Макрос STAILQ_CONCAT объединяет хвостовую очередь, возглавляемую head2, с концом очереди, возглавляемой head1, удаляя все записи из первой.

Макрос STAILQ_SWAP меняет местами содержимое head1 и head2.

  // Заголовок односвязной хвостовой очереди
  STAILQ_HEAD(stailhead, entry) head = STAILQ_HEAD_INITIALIZER(head);
  struct stailhead *headp;
  
  // Объявляем структуру для хранения данных
  struct entry {
    uint32_t data1;
    uint32_t data2;
    uint32_t data3;
    char string[32];
    STAILQ_ENTRY(entry) next;
  } *n1, *n2, *n3, *np, *np_temp;

  // Инициализация очереди
  STAILQ_INIT(&head);                    

  // Добавляем элемент в самое начало очереди
  n1 = malloc(sizeof(struct entry));
  STAILQ_INSERT_HEAD(&head, n1, next);

  // Добавляем элемент в самый конец очереди
  n1 = malloc(sizeof(struct entry));
  STAILQ_INSERT_TAIL(&head, n1, next);

  // Добавляем элемент после указанного
  n2 = malloc(sizeof(struct entry));
  STAILQ_INSERT_AFTER(&head, n1, n2, next);
                                         
  // Удаление элемента из списка
  STAILQ_REMOVE(&head, n2, entry, next);
  free(n2);

  // Удаление элемента из начала списка        
  n3 = STAILQ_FIRST(&head);
  STAILQ_REMOVE_HEAD(&head, next);
  free(n3);

  // Сканирование вперед
  STAILQ_FOREACH(np, &head, next) {
    // Что-то делаем
    // np-> ...
  };

  // Безопасное сканирование с удалением элементов                  
  STAILQ_FOREACH_SAFE(np, &head, next, np_temp) {
     STAILQ_REMOVE(&head, np, entry, next);
     free(np);
  };
                                         
  // Ещё один способ удаления элементов - с головы
  while (!STAILQ_EMPTY(&head)) {
    n1 = STAILQ_FIRST(&head);
    STAILQ_REMOVE_HEAD(&head, next);
    free(n1);
  }

  // Быстрое удаление
  n1 = STAILQ_FIRST(&head);
  while (n1 != NULL) {
    n2 = STAILQ_NEXT(n1, next);
    free(n1);
    n1 = n2;
  };
  STAILQ_INIT(&head);

 

Двусвязный список

Список возглавляет структура, определяемая макросом LIST_HEAD. Эта структура содержит единственный указатель на первый элемент в списке. Элементы связаны между собой двойными связями, так что произвольный элемент может быть удален без необходимости обхода всего списка. Новые элементы могут быть добавлены в список после существующего элемента, перед существующим элементом или в начало списка. Структура LIST_HEAD объявляется следующим образом:

LIST_HEAD(HEADNAMETYPEhead;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, которые должны быть связаны со списком. Указатель на начало списка позже может быть объявлен как:

struct HEADNAME *headp;

Имена head и headp выбираются программистом самостоятельно.

  • Макрос LIST_ENTRY объявляет структуру, которая соединяет элементы в списке.
  • Макрос LIST_HEAD_INITIALIZER оценивается как инициализатор для заголовка списка (например NULL).
  • Макрос LIST_INIT инициализирует список, на который ссылается head.
  • Макрос LIST_EMPTY принимает значение true, если в списке нет элементов.
  • Макрос LIST_FIRST возвращает первый элемент в списке или NULL, если список пуст.
  • Макрос LIST_PREV возвращает предыдущий элемент в списке.
  • Макрос LIST_NEXT возвращает следующий элемент в списке.
  • Макрос LIST_INSERT_HEAD вставляет новый элемент elm в начало списка.
  • Макрос LIST_INSERT_AFTER вставляет новый элемент elm после элемента listelm.
  • Макрос LIST_INSERT_BEFORE вставляет новый элемент elm перед элементом listelm.
  • Макрос LIST_REMOVE удаляет элемент elm из списка.
  • Макрос LIST_SWAP меняет местами содержимое head1 и head2.
  • Макрос LIST_FOREACH просматривает список, на который ссылается head, в прямом направлении, присваивая каждому элементу по очереди var.
  • Макрос LIST_FOREACH_FROM ведет себя идентично LIST_FOREACH(), когда var имеет значение NULL. В противном случае он обрабатывает var как ранее найденный элемент LIST и начинает цикл с var вместо первого элемента в LIST, на который ссылается head. То есть его можно использовать для продолжения просмотра списка.
  • Макрос LIST_FOREACH_SAFE обходит список, на который ссылается заголовок head, в прямом направлении, присваивая каждый элемент по очереди var. Однако, в отличие от LIST_FOREACH(), здесь разрешено как удалять var, так и безопасно освобождать ее внутри цикла, не мешая обходу.
  • Макрос LIST_FOREACH_FROM_SAFE ведет себя идентично LIST_FOREACH_SAFE(), когда var имеет значение NULL, в противном случае он обрабатывает var как ранее найденный элемент LIST и начинает цикл с var вместо первого элемента в LIST, на который ссылается head.
  // Заголовок списка
  LIST_HEAD(listhead, entry) head = LIST_HEAD_INITIALIZER(head);
  struct listhead *headp;                

  // Объявляем структуру для хранения данных
  struct entry {
    uint32_t data1;
    uint32_t data2;
    uint32_t data3;
    char string[32];
    LIST_ENTRY(entry) next;
  } *n1, *n2, *n3, *np, *np_temp;

  // Инициализация списка
  LIST_INIT(&head);                    

  // Добавляем элемент в самое начало списка
  n1 = malloc(sizeof(struct entry));
  LIST_INSERT_HEAD(&head, n1, next);

  // Добавляем элемент после указанного
  n2 = malloc(sizeof(struct entry));
  LIST_INSERT_AFTER(n1, n2, next);

  // Добавляем элемент перед указанным
  n3 = malloc(sizeof(struct entry));
  LIST_INSERT_BEFORE(n2, n3, next);

  // Удаление элемента из списка
  LIST_REMOVE(n2, next);
  free(n2);

  // Сканирование вперед
  LIST_FOREACH(np, &head, next) {
    // Что-то делаем
    // np-> ...
  };

  // Безопасное сканирование с удалением элементов                  
  LIST_FOREACH_SAFE(np, &head, next, np_temp) {
     LIST_REMOVE(np, next);
     free(np);
  };

  // Ещё один способ удаления элементов - с головы
  while (!LIST_EMPTY(&head)) {
    n1 = LIST_FIRST(&head);
    LIST_REMOVE(n1, next);
    free(n1);
  }

  // Быстрое удаление
  n1 = LIST_FIRST(&head);
  while (n1 != NULL) {
    n2 = LIST_NEXT(n1, next);
    free(n1);
    n1 = n2;
  };
  LIST_INIT(&head);

Хвостовая очередь

Хвостовую очередь возглавляет структура, определяемая макросом TAILQ_HEAD . Эта структура содержит пару указателей, один на первый элемент в хвостовой очереди, а другой на последний элемент в хвостовой очереди. Элементы так же дважды связаны между собой, так что произвольный элемент может быть удален без обхода всей хвостовой очереди. Новые элементы могут быть добавлены в хвостовую очередь после существующего элемента, перед существующим элементом, в начале хвостовой очереди или в конце хвостовой очереди. Структура TAILQ_HEAD объявляется следующим образом:

TAILQ_HEAD(HEADNAMETYPEhead;

где HEADNAME — имя определяемой структуры, а TYPE — тип элементов, которые должны быть связаны с очередью. Указатель на начало очереди позже может быть объявлен как:

struct HEADNAME *headp;

Имена head и headp выбираются программистом самостоятельно.

  • Макрос TAILQ_HEAD_INITIALIZER оценивается как инициализатор хвостовой очереди head.
  • Макрос TAILQ_ENTRY объявляет структуру, которая соединяет элементы в хвостовой очереди.
  • Макрос TAILQ_INIT инициализирует хвостовую очередь, на которую ссылается head.
  • Макрос TAILQ_EMPTY принимает значение true, если в хвостовой очереди нет элементов.
  • Макрос TAILQ_LAST возвращает последний элемент в хвостовой очереди. Если хвостовая очередь пуста, возвращаемое значение равно NULL.
  • Макрос TAILQ_FIRST возвращает первый элемент в хвостовой очереди или NULL, если хвостовая очередь пуста.
  • Макрос TAILQ_NEXT возвращает следующий элемент в хвостовой очереди или NULL, если этот элемент является последним.
  • Макрос TAILQ_PREV возвращает предыдущий элемент в хвостовой очереди или NULL, если этот элемент является первым.
  • Макрос TAILQ_INSERT_HEAD вставляет новый элемент elm в начало хвостовой очереди.
  • Макрос TAILQ_INSERT_TAIL вставляет новый элемент elm в конец хвостовой очереди.
  • Макрос TAILQ_INSERT_AFTER вставляет новый элемент elm после элемента listelm.
  • Макрос TAILQ_INSERT_BEFORE вставляет новый элемент elm перед элементом listelm.
  • Макрос TAILQ_REMOVE удаляет элемент elm из хвостовой очереди.
  • Макрос TAILQ_CONCAT объединяет хвостовую очередь, возглавляемую head2, с концом очереди, возглавляемой head1, удаляя все записи из первой.
  • Макрос TAILQ_FOREACH обходит хвостовую очередь, на которую ссылается head, в прямом направлении, присваивая каждому элементу по очереди var. var устанавливается в NULL, если цикл завершается нормально или если элементов не было.
  • Макрос TAILQ_FOREACH_REVERSE проходит хвостовую очередь, на которую ссылается head, в обратном направлении, присваивая каждый элемент по очереди var.
  • Макросы TAILQ_FOREACH_SAFE и TAILQ_FOREACH_REVERSE_SAFE обходят список, на который ссылается заголовок head, в прямом или обратном направлении соответственно, присваивая каждый элемент по очереди var. Однако, в отличие от своих небезопасных аналогов, TAILQ_FOREACH и TAILQ_FOREACH_REVERSE позволяют как удалить переменную var, так и безопасно освободить ее внутри цикла, не мешая обходу.
  // Заголовок хвостовой очереди
  TAILQ_HEAD(tailhead, entry) head = TAILQ_HEAD_INITIALIZER(head);
  struct tailhead *headp;                

  // Объявляем структуру для хранения данных
  struct entry {
    uint32_t data1;
    uint32_t data2;
    uint32_t data3;
    char string[32];
    TAILQ_ENTRY(entry) next;
  } *n1, *n2, *n3, *np, *np_temp;

  // Инициализация очереди
  TAILQ_INIT(&head);                    

  // Добавляем элемент в самое начало очереди
  n1 = malloc(sizeof(struct entry));
  TAILQ_INSERT_HEAD(&head, n1, next);

  // Добавляем элемент в самый конец очереди
  n1 = malloc(sizeof(struct entry));
  TAILQ_INSERT_TAIL(&head, n1, next);

  // Добавляем элемент после указанного
  n2 = malloc(sizeof(struct entry));
  TAILQ_INSERT_AFTER(&head, n1, n2, next);

  // Добавляем элемент перед указанным
  n3 = malloc(sizeof(struct entry));
  TAILQ_INSERT_BEFORE(n2, n3, next);

  // Удаление элемента из списка
  TAILQ_REMOVE(&head, n3, next);
  free(n3);

  // Сканирование вперед
  TAILQ_FOREACH(np, &head, next) {
    // Что-то делаем
    // np-> ...
  };

  // Безопасное сканирование с удалением элементов                  
  TAILQ_FOREACH_SAFE(np, &head, next, np_temp) {
     TAILQ_REMOVE(&head, np, next);
     free(np);
  };

  // Ещё один способ удаления элементов - с головы
  while (!TAILQ_EMPTY(&head)) {
    n1 = TAILQ_FIRST(&head);
    TAILQ_REMOVE(&head, n1, next);
    free(n1);
  }

  // Быстрое удаление
  n1 = TAILQ_FIRST(&head);
  while (n1 != NULL) {
    n2 = TAILQ_NEXT(n1, next);
    free(n1);
    n1 = n2;
  };
  TAILQ_INIT(&head);

 

Ссылки

  1. Исходники для данной статьи: https://github.com/kotyara12/dzen/blob/master/queues/src/main.c
  2. Пример использования очереди в ESP-IDF – очередь отправки MQTThttps://github.com/espressif/esp-mqtt/blob/master/lib/mqtt_outbox.c
  3. Пример использования связных списков для хранения параметров: https://github.com/kotyara12/reParams/blob/master/src/reParams.cpp
  4. Пример использования связных списков для хранения настроек зон охраны в ОПС: https://github.com/kotyara12/reAlarm/blob/master/src/reAlarm.cpp

📌 Полный архив статей вы найдете здесь


Пожалуйста, оцените статью:
[ 5 из 5, всего 1 оценок ]

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *