LINUX.ORG.RU

Красивая реализация списков


0

2

И так, после некоторых экспериментов я пришел к следующей реализации списков, которая включает в себя все лучшее из стандартной реализации и реализации «как в линуксе» :)

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

В настоящий момент в каждую сруктуру, которая может включаться в список нужно добавить служебный элемент для связи (макрос LIST_LINK), который всегда называется link. Если во все макросы добавить название служебного поля как параметр, то можно иметь один и то-же элемент в разных списках (как в линуксе), мне это не нужно, поэтому не делал.

Конструктивная критика приветствуется.

Сама реализация:

#ifndef _LIST_H_

#define _LIST_H_


#define LIST_LINK(type) \
  struct { struct type *next;  struct type *prev; } link


#define DECLARE_LIST(type, name) \
  type name = { .link = { .next = &(name), .prev = &(name) } }


#define LIST_INIT(ptr) \
  do { (ptr)->link.next = ptr; (ptr)->link.prev = ptr; } while (0)


#define list_add_after(_item, _new) \
  do { \
    (_new)->link.next = (_item)->link.next; \
    (_new)->link.prev = (_item); \
    (_item)->link.next->link.prev = (_new); \
    (_item)->link.next = (_new); \
  } while (0)

#define list_add_before(_item, _new) \
  do { \
    (_new)->link.next = (_item); \
    (_new)->link.prev = (_item)->link.prev; \
    (_item)->link.prev->link.next = (_new); \
    (_item)->link.prev = (_new); \
  } while (0)

#define list_add(_list, _new) \
  list_add_before(_list, _new)

#define list_add_head(_list, _new) \
  list_add_after(_list, _new)

#define list_del(_item) \
  do { \
    (_item)->link.prev->link.next = (_item)->link.next; \
    (_item)->link.next->link.prev = (_item)->link.prev; \
    (_item)->link.next = NULL; \
    (_item)->link.prev = NULL; \
  } while (0)

#define list_del_init(_list) \
  do { \
    list_del(_item); \
    LIST_INIT(_item); \
  } while (0)

#define list_is_empty(_list) \
  ((_list)->link.next == (_list))


#define list_foreach(_var, _list) \
  for (_var = (_list)->link.next; _var != (_list); _var = _var->link.next)

#define list_foreach_back(_var, _list) \
  for (_var = (_list)->link.prev; _var != (_list); _var = _var->link.prev)

#define list_foreach_var(_type, _var, _list) \
  for (_type *_var = (_list)->link.next; _var != (_list); _var = _var->link.next)

#define list_foreach_back_var(_type, _var, _list) \
  for (_type *_var = (_list)->link.prev; _var != (_list); _var = _var->link.prev)

#define list_foreach_safe(_var, _list, _tmp) \
  for (_var = (_list)->link.next, _tmp = (_var)->link.next; _var != (_list); \
       _var = _tmp, _tmp = (_var)->link.next)

#define list_foreach_back_safe(_var, _list, _tmp) \
  for (_var = (_list)->link.prev, _tmp = (_var)->link.prev; _var != (_list); \
       _var = _tmp, _tmp = (_var)->link.prev)

#endif // _LIST_H_


Пример использования:

#include <stdio.h>
#include <stdlib.h>
#include "list.h"

typedef struct num_t
{
  int      num;
  LIST_LINK(num_t);
} num_t;

static DECLARE_LIST(num_t, nums);

int main(void)
{
  num_t n1, n2, n3, n4;
  num_t *n5;
  num_t *ext_var;

  n1.num = 1111;
  n2.num = 2222;
  n3.num = 3333;
  n4.num = 4444;

  n5 = malloc(sizeof(num_t));
  n5->num = 5555;

  list_add(&nums, &n1);
  list_add(&nums, &n2);
  list_add(&nums, &n3);
  list_add(&nums, &n4);
  list_add(&nums, n5);

  list_del(&n2);

  printf("Go through list using c99 style for loop declaration:\n");
  list_foreach_var (num_t, var, &nums)
  {
    printf("  %d\n", var->num);
  }

  printf("Go through list using external variable as loop parameter:\n");
  list_foreach (ext_var, &nums)
  {
    printf("  %d\n", ext_var->num);
  }

  return 0;
}

★★★★

Еще одно достоинство, что элементы списка имеют свой тип, а не struct list_head, что удобно при передаче списков по нескольким вложенным функциям.

alexru ★★★★
() автор топика
Ответ на: комментарий от ananas

Честно, не осилил. Там такое плотное использование препроцессора, что крыша едет разбираться.

alexru ★★★★
() автор топика
Ответ на: комментарий от enep

Это единственный вариант не зависеть от типа стрктур, достаточно, чтобы то, что подсунули в качестве параметров имело поле link. Кроме того все-равно почти все реализации делают функции работы со списками inline.

alexru ★★★★
() автор топика

Оно чем-то отличается от ядерных, кроме того, что ты зафиксировал одну единственную структуру с указателями?

dmsh
()
Ответ на: комментарий от dmsh

Да, тем что не нужно иметь вот этого:

#define list_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

Работа со списком идет естественным образом. А в ядре все элементы списка имееют тип struct list_head и приходится с помощью того, что выше вычислять адрес стуктуры исползуя ее тип. В моей реализации тип вообще знать не нужно.

alexru ★★★★
() автор топика

> typedef struct num_t

{
int num;
LIST_LINK(num_t);
} num_t;

а если num_t должен входить в два и более списков? это сплошь и рядом нужно.

static DECLARE_LIST(num_t, nums);

то есть, это «голова» списка, элементом не является. при этом sizeof как у элемента, нот гут.

idle ★★★★★
()
Ответ на: комментарий от alexru

То что структура только одна - это легко поправимо, но лично мне не нужно.

alexru ★★★★
() автор топика

А чем эта реализация (кроме замены функций на макросы) отличается от классической у K&R? Кстати, не лучше было бы заменить макроопределения на inline-функции (по сути - то же самое, но зато есть проверка типов аргументов, и какую-нибудь ошибку будет проще выявить).

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от idle

а если num_t должен входить в два и более списков? это сплошь и рядом нужно.

Значит в каждый макрос нужно передать идентификатор списка, и везде вместо ->link использовать его.

то есть, это «голова» списка, элементом не является. при этом sizeof как у элемента, нот гут.

Да, есть такое.

alexru ★★★★
() автор топика
Ответ на: комментарий от alexru

> вычислять адрес стуктуры исползуя ее тип

да, но все очень удобно. не так уж часто нужно пользоваться list_entry() напрямую.

idle ★★★★★
()
Ответ на: комментарий от Eddy_Em

А чем эта реализация (кроме замены функций на макросы) отличается от классической у K&R?

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

по сути - то же самое, но зато есть проверка типов аргументов, и какую-нибудь ошибку будет проще выявить

Нет, не тоже, отказаться от знания типа было ондной из основных идей.

alexru ★★★★
() автор топика
Ответ на: комментарий от alexru

> Значит в каждый макрос нужно передать идентификатор списка,

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

и везде вместо ->link использовать его.


ага, и list_foreach() надо менять.

idle ★★★★★
()
Ответ на: комментарий от idle

да, но все очень удобно. не так уж часто нужно пользоваться list_entry() напрямую.

2245 раз в последнем ядре.

Но не в этом суть. Неудобно, когда по иерархии функции передаешь списки, они все имеют тип struct list_head, и приходится постоянно думать какой именно тип используется в данном месте. В моем случае передается настоящий тип.

alexru ★★★★
() автор топика
Ответ на: комментарий от idle

Я же написал, что это легко можно доделать, но лично мне конкретно сейчас это не нужно. Менять нужно все макросы, естественно.

alexru ★★★★
() автор топика

И еще что плохо в ядеерной реализации - это то, что когда в структуре видишь что-то типа

struct list_head	poll_list;
непонятно - это элемент для вклюяения в другие списки или голова нового списка.

alexru ★★★★
() автор топика
Ответ на: комментарий от alexru

> 2245 раз в последнем ядре.

на сколько строк?

ну вот, напр list_entry(head->lru.next) часто
встречается. можно было бы просто один раз
сделать inline page_next_lru() helper.

они все имеют тип struct list_head


есть такое дело. согласен.

idle ★★★★★
()
Ответ на: комментарий от alexru

> Честно, не осилил. Там такое плотное использование препроцессора, что крыша едет разбираться.

man 3 queue и крыша на месте

ananas ★★★★★
()
Ответ на: комментарий от alexru

мне больше другое не нравится. даже если знаешь,
что это head а не node, не сразу понятно, что
там может висеть.

тем не менее, у такого подхода есть и много
достоинств. ну, хотя бы потому, что код один
и тот же. у вас же, фактически, template.

ладно, я завязываю этот флейм ;) you have a
point, я не спорю.

idle ★★★★★
()
Ответ на: комментарий от Boy_from_Jungle

Нет, но если изобретение велосипеда позволет мне быстрее добраться туда куда нужно, я буду делать велосипед.

alexru ★★★★
() автор топика

можно и malloc спрятать, как в одном из моих студенческих;) :

% cat List.h

#ifndef _LIST_H
#define _LIST_H

struct list_node{
   void *data;
   struct list_node *next;
};

struct list{
   struct list_node *head;
   struct list_node *tail;
};

void my_list_add(struct list *const list, void *const data);
void *my_list_get(const struct list *list, void* sample, int (*criteria)(void* sample, void* data));
void my_list_iterate(const struct list *list, void (*todo_impl)(void *data));
void my_list_delete(struct list *list);
void *my_list_get_first(const struct list *list);

#endif


% cat List.c

#include «common.h»
#include «List.h»

void my_list_add(struct list *const list, void *const data){
   
   struct list_node *new_node=(struct list_node *)malloc(sizeof(struct list_node));
   
   if(!data){
      fprintf(stderr, «__FILE__: Not adding null data»);
      return;
   }
   
   if(new_node == NULL)
      OUT_OF_MEM();
   
   new_node->next = NULL;
   new_node->data = data;
   
   if((list->head == NULL)){ /* empty list */
      list->head = list->tail = new_node;
   }
   else if(list->head == list->tail){ /* single element in the list */
      list->head->next = new_node;
      list->tail = new_node;
   }
   else{ /* >1 element */
      list->tail->next = new_node;
      list->tail = new_node;
   }
}


void *my_list_get(const struct list *list, void* sample, int (*criteria)(void* sample, void* data)){
   
   struct list_node *cur;
   
   if(list->head == NULL)
      return NULL; /* empty list */
   
   cur=list->head;
   
   while(cur){
      if((*criteria)(sample, cur->data)==0){
         return cur->data;
      }
      cur = cur->next;
   }
   
   return NULL;
}


void *my_list_get_first(const struct list *list){
   
   if(list->head == NULL)
      return NULL; /* empty list */

   return list->head->data;
}


void my_list_iterate(const struct list *list, void (*todo_impl)(void* data)){
   
   struct list_node *cur;   
   
   if(list->head == NULL)
      return; /* empty list */
   
   cur=list->head;
   while(cur){
      (*todo_impl)(cur->data);
      cur = cur->next;
   }
}


void my_list_delete(struct list *list){
   
   struct list_node *cur;
   
   if(list->head == NULL)
      return; /* empty list */
   
   while(list->head->next){
      cur=list->head;
      list->head = list->head->next;
      free(cur);
   }
   free(list->head);
   free(list);
}

siberean
()
Ответ на: комментарий от siberean

а не лучше ли тут сначала проверять, а потом выделять память?

void my_list_add(struct list *const list, void *const data){
   
   struct list_node *new_node=(struct list_node *)malloc(sizeof(struct list_node));
   
   if(!data){
      fprintf(stderr, "__FILE__: Not adding null data");
      return;
   }
anonymous
()
Ответ на: комментарий от siberean

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

osox
()
Ответ на: комментарий от anonymous

конечно лучше :)
(Те 2 строки надо поменять местами)
я же написал - студенческий код.

siberean
()
Ответ на: комментарий от alexru

в структуре должны быть поля prev и next, тогда уж можно условиться чтобы они всегда были в начале структуре, тогда любую структуру можно привести к типу структуры struct node { struct node *next; struct node *prev } или даже struct something { struct virtualtable * vt}

dimon555 ★★★★★
()

Зашкаливает количество макросов, поэтому УГ. А на C++ подобное реализовывается штатными средствами.

Deleted
()
Ответ на: комментарий от Deleted

> А на C++ подобное реализовывается штатными средствами

показывай.

не. я, ессно, не утверждаю, что на C++ этого не сделать.
но вот почему-то я ни разу не видел, чтобы где-то это
было сделано прилично.

правда, мне очень давно (к щастью ;) не приходилось
сталкиваться с C++.

idle ★★★★★
()
Ответ на: комментарий от dimon555

в структуре должны быть поля prev и next, тогда уж можно условиться чтобы они всегда были в начале структуре,

1. Может потребоваться (и на самом деле уже потребовалось), чтобы одна и та-же структура входила в несколько списков.

2. С приведенями типов могут случаться неприятности типа http://www.linux.org.ru/forum/development/5554296.

alexru ★★★★
() автор топика
Ответ на: комментарий от alexru

> Честно, не осилил. Там такое плотное использование препроцессора, что крыша едет разбираться.

Что не осилил-то? Сам же написал то же самое, только не все фичи реализовал, но тоже на макросах. Хорошо, что написал, но использовать лучше sys/queue.h - больше шансов, что другой поймёт написанное.

const86 ★★★★★
()
Ответ на: комментарий от idle

показывай.

например http://boost.org/doc/libs/1_42_0/doc/html/intrusive/usage.html

впрочем, мне не понятно, зачем вообще нужны интрузивные списки; может ты расскажешь?

если же взять те представления, что я о них имею, то получится что-то вроде

template<class Payload> class IntrusiveList
{
public:
  Payload* add(Payload* list) { next_=list; return next_; }
  Payload* next() { return next_; }
  typedef Payload PayloadType;
protected:
  IntrusiveList(): next_(0) { }  /// нефиг создавать "просто" IntrusiveList без Payload-а
private:
  Payload* next_;
};

#define for_each( element, list ) for( typeof(list)  element=list; element!=NULL; element=element->next() ) /// gcc-изм однако

/// usage:

struct SomeStruct;
struct SomeStruct: public IntrusiveList<SomeStruct>
{
  int value;
  SomeStruct(int value): value(value) {}
};

#include <iostream>

int main()
{
  SomeStruct *list = new SomeStruct(0);
  list->add(new SomeStruct(1))->add(new SomeStruct(2))->add(new SomeStruct(3));

  for_each( element, list )
    std::cout << element->value << ' ' << element <<  ' ' << element->next() << '\n'; 
  return 0;
}

список связан в одну сторону чтобы писать попроще

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

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

цетральная структура ссылается на загруженные модули
typedef struct _PEB_LDR_DATA
{
    ULONG      Length;
    BOOLEAN    Initialized;
    PVOID      SsHandle;
    LIST_ENTRY InLoadOrderModuleList;           // голова в порядке загрузки
    LIST_ENTRY InMemoryOrderModuleList;         // голова в порядке расположения в памяти
    LIST_ENTRY InInitializationOrderModuleList; // голова в порядке инициализации
} PEB_LDR_DATA, *PPEB_LDR_DATA;

структура описывающая отдельный модуль
typedef struct _LDR_MODULE_ENTRY {
    LIST_ENTRY         InLoadOrderModuleList;    // связи пронизывают модули
    LIST_ENTRY         InMemoryOrderModuleList;
    LIST_ENTRY         InInitializationOrderModuleList;
    PVOID              ModuleBase;         // module base address
    PVOID              ModuleEntry;        // module entry point
    ULONG              ModuleSize;         // module size (?)
    UNICODE_STRING     ModulePath;         // module full path
    UNICODE_STRING     ModuleName;         // module name
    ...
} LDR_MODULE_ENTRY, * PLDR_MODULE_ENTRY;
нужно чтобы структура описывающая модуль была сразу в трех списках можно продублировать структуры неэффективно в плане памяти а можно заюзать интузивные списки

osox
()
Ответ на: комментарий от osox

нужно чтобы структура описывающая модуль была сразу в трех списках можно продублировать структуры неэффективно в плане памяти а можно заюзать интузивные списки

и нафига дублировать структуры? берется три обычных неитрузивных списка; оверхед будет в 3*sizeof(void*) лишних байт на каждую структуру; сами же структуры у тебя порядком большие, особенно с учетом юникодных строк

если позарез жалко лишних 3*sizeof(void*), то делаются 3 интрузивных списка с тэгами:

class ByLoadOrder {}; 
class ByMemoryOrder {};
class ByInitializationOrder {};

struct SomeStruct;
struct SomeStruct:
    public IntrusiveList<SomeStruct, ByLoadOrder>,
    public IntrusiveList<SomeStruct, ByMemoryOrder>,
    public IntrusiveList<SomeStruct, ByInitializationOrder>,
{
    PVOID              ModuleBase;         // module base address
    PVOID              ModuleEntry;        // module entry point
    ULONG              ModuleSize;         // module size (?)
    UNICODE_STRING     ModulePath;         // module full path
    UNICODE_STRING     ModuleName;         // module name
};

/// сходу не скажу, но видимо можно будет так:

list->add<ByMemoryOrder>(new SomeStruct());

может, можно и покрасивее сделать, типа

enum ModuleOrder { ByLoadOrder, ByMemoryOrder,  ByInitializationOrder };

struct SomeStruct;
struct SomeStruct:
    public IntrusiveList<SomeStruct, ModuleOrder>,
{
    PVOID              ModuleBase;         // module base address
    PVOID              ModuleEntry;        // module entry point
    ULONG              ModuleSize;         // module size (?)
    UNICODE_STRING     ModulePath;         // module full path
    UNICODE_STRING     ModuleName;         // module name
};

...

list->add<ByMemoryOrder>(new SomeStruct());
www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

да можно хранить и указатели но оверхед все равно будет и не в 3*sizeof(void*) лишних байт на каждую структуру; а на каждый выделенный блок еще служебная инфа прибавится и будет sizeof(void*)*3 + sizeof((скрытый блок*3)) * на каждую структуру

osox
()
Ответ на: комментарий от www_linux_org_ru

p = malloc оверхед несколько байт перед возвращенным указателем там размер блока в дебаге еще что то

osox
()
Ответ на: комментарий от osox

> p = malloc оверхед несколько байт перед возвращенным указателем там размер блока в дебаге еще что то

3 «списочных» указателя + 1 указатель на pаyload прекрасно умещаются в 16 байт, т.е. паддинг не нужен; нормальные маллоки не транжирят место на идиотские заголовки и не держат связанные списки, а держат bitmap занятых блоков для размещения столь малых объектов (все равно делить 16 байт уже почти бестолку) — т.е. оверхед будет 1-2 бита (ну в крайнем случае 4 бита, но это уже расточительство)

хотя, впрочем, если ты скажешь «локальность памяти», то я, возможно, соглашусь

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> struct SomeStruct: public IntrusiveList<SomeStruct>

понятно.

вменяюмую реализацию на цпп я не увижу никогда.

зы: еще раз, я не утверждаю что ее нет, или она
невозможна.

idle ★★★★★
()

велосипедостоитель! чем тебя sys/queue.h не устпроил?

beastie ★★★★★
()
Ответ на: комментарий от idle

> вменяюмую реализацию на цпп я не увижу никогда

и чем же показанная реализация «невменяема»?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от idle

>> struct SomeStruct: public IntrusiveList<SomeStruct>

понятно

подозреваю, что ты прочитал это как

struct SomeStruct: public IntrusiveList

тогда бы действительно получилось фуфло, т.к. без даункастов было бы не обойтись; щас же даункасты не нужны

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> подозреваю, что ты прочитал это как



struct SomeStruct: public IntrusiveList



нет.

тогда бы действительно получилось фуфло


честно говоря, оно и так фуфло ;) имхо, конечно.

мало того, что для реального использования это не годится.
оно еще и наглядный пример того, как не нужно пользоваться
наследованием. но это, опять-таки, это только мое мнение.

аргументировать лень, можно засчитывать мне слив.

idle ★★★★★
()
Ответ на: комментарий от idle

> аргументировать лень, можно засчитывать мне слив.

а че еще остается?

я правда нифига интрузивными списками не пользовался, так что наляпал что-то, лишь бы тайпчекнулось и без даункастинга, и надеялся, что ты действительно найдешь проблемы; значит, будем считать, что проблем нет

мало того, что для реального использования это не годится.

для реального использования полезнее дважды-связанные списки, но их сделать чуть заморочнее; впрочем, не вижу там проблем с типами

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

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> интрузивными списками

кстати, а что это?

для реального использования полезнее

дважды-связанные списки



и то, и другое нужно, дело не в этом.

для реального использование нужна, как минимум,
поддержка множества списков.

(хм, только что заметил, это уже было сказано)

у кого аллергия на плюсы


у меня ее нет. но и любви особой нет.

а вот на такой код у меня аллергия, да ;) ну елки,
наследование полезная вещь, но не в каждой же дырке
затычка.

мне просто было интересно про «штатные средства» в
http://www.linux.org.ru/jump-message.jsp?msgid=5561350&cid=5562507

idle ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.