LINUX.ORG.RU

История изменений

Исправление MOPKOBKA, (текущая версия) :

А вы как свои (или не свои) контейнеры организовываете?

Массив как структура включающая поля:

struct array {
  void *data;

  // size это то что обычно понимается под размером массива, 
  // если в массиве 5 элементов, то и size будет равен 5
  //
  // capacity это размер выделенной памяти, в пустом массиве 
  // это 0, в массиве до с 1..16 элементами capacity будет 16,
  // в массиве с 24 элементами 32, и далее так она и растет 
  // 0, 16, 32, 64, 128, 256, 512, ... Такой подход позволяет
  // не вызывать каждый раз realloc при добавлении элемента, а
  // только когда size достигает capacity, тогда capacity 
  // увеличивается в два раза, и можно черпать новые элементы
  //
  // Полезно заранее выставить capacity к примеру в 1000 
  // элементов, что бы обойтись в среднем парой вызовов realloc
  // на всю жизнь массива
  size_t capacity 
  size_t size;

  // Можно постоянно передавать размер элемента через аргументы
  // но мне это не нравится
  size_t element_size;
};

Еще часто использую pool элементов:

// Этот union не будет работать, но суть в том что
// освобожденные элементы образуют список из которого можно
// брать элементы для выделения. Вместо union просто type cast. 
union slot {
  uint32_t prev_free;
  char element[];
};

// Пул строится на массиве, а значит он так же поддерживает
// capacity который можно заранее установить
//
// Еще одно его преимущество для выделения множества объектов,
// это то, что пул можно убить одним free, вместе со всеми его 
// элементами, а не проходится по каждому элементу с free если
// каждый отдельный выделен через обычный malloc
struct pool {
  union slot *memory;
  size_t capacity;
  size_t size;
  size_t element_size;
  
  // При realloc указатели сбиваются, поэтому хорошо иметь
  // флаг запрещающий делать realloc после инициализации с 
  // определенным размером
  bool lock;
};

// Из пула можно выделять элементы, быстрее чем через malloc и
// уплотненно, так как нету никаких заголовков или 
// дополнительного выравнивания
void *pool_alloc(struct pool *self);

// Можно освобождать элементы, и при выделении новых (alloc)
// ячейки освобожденных элементов будут переиспользованы
void pool_free(struct pool *self, void *p);

// Можно брать индексы элементов в пуле, и хранить не указатели
// на элементы, а их индексы. Использование индексов вместо
// указателей, позволяет сократить использование памяти если
// указателей было много, uint32_t - 4 byte, pointer - 8 byte, 
// и в отличие от указателей, при realloc данных пула,  индексы
// будут указывать все на тот же объект
uint32_t pool_id(struct pool *self, void *p);

// Получение элемента по его индексу
void *pool_at(struct pool *self, uint32_t id);

Вместо привычных списков, использую интрузивные списки

struct list {
  struct list *prev;
  struct list *next;
  // Все, никаких данных он сам по себе не хранит!
};

// Функции для работы с ними довольно очевидные
...
void list_append(struct list *parent, struct list *child);
...

// А теперь к использованию, выдуманная структура игрока для
// примера
struct player {
  char name[128];
  uint32_t score;

  // Встраивается список таким способом 
  struct list lst;
};

// Теперь можно создать игроков A, B, C, и сделать из них
// список
struct list *players;

players = &A.lst;
list_append(players, &B.lst);
list_append(players, &C.lst);

// Имея указатель на список, и зная структуру на которой он 
// закреплен, легко получить указатель на структуру, достаточно
// вычесть адрес поля списка относительно самой структуры, для 
// этого обычно делается макрос
//
// struct player {
//   char name[128];  <-- 128 byte
//   uint32_t score;  <-- 4 byte
//   struct list lst; <-- 132
//                        player = (list_ptr - 132)
// };

У них есть ф-ция container_print(FILE* out, CONTAINER *c).

У меня обычно нету надобности в печати контейнера, смотрю через gdb состояния при отладке, ну и если нужно функции импорта/экспорта, но они не общие для всего.

PS Не совсем понял про какой слой

Я подумал все это делается ради того что бы не набирать дополнительно «%q\n», и мне это показалось неоправданным ради экономии пары символов, но теперь я вижу что цель была другая, обобщенное программирование, тут сразу вспоминаются такие проекты:

https://rurban.github.io/ctl/ - тоже попытка повторить шаблоны

https://libcello.org/ - попытка сделать С более высокоуровневым

Исправление MOPKOBKA, :

А вы как свои (или не свои) контейнеры организовываете?

Массив как структура включающая поля:

struct array {
  void *data;

  // size это то что обычно понимается под размером массива, 
  // если в массиве 5 элементов, то и size будет равен 5
  //
  // capacity это размер выделенной памяти, в пустом массиве 
  // это 0, в массиве до с 1..16 элементами capacity будет 16,
  // в массиве с 24 элементами 32, и далее так она и растет 
  // 0, 16, 32, 64, 128, 256, 512, ... Такой подход позволяет
  // не вызывать каждый раз realloc при добавлении элемента, а
  // только когда size достигает capacity, тогда capacity 
  // увеличивается в два раза, и можно черпать новые элементы
  //
  // Полезно заранее выставить capacity к примеру в 1000 
  // элементов, что бы обойтись в среднем парой вызовов realloc
  // на всю жизнь массива
  size_t capacity 
  size_t size;

  // Можно постоянно передавать размер элемента через аргументы
  // но мне это не нравится
  size_t element_size;
};

Еще часто использую pool элементов:

// Этот union не будет работать, но суть в том что
// освобожденные элементы образуют список из которого можно
// брать элементы для выделения. Вместо union просто type cast. 
union slot {
  uint32_t prev_free;
  char element[];
};

// Пул строится на массиве, а значит он так же поддерживает
// capacity который можно заранее установить
//
// Еще одно его преимущество для выделения множества объектов,
// это то, что пул можно убить одним free, вместе со всеми его 
// элементами, а не проходится по каждому элементу с free если
// каждый отдельный выделен через обычный malloc
struct pool {
  union slot *memory;
  size_t capacity;
  size_t size;
  size_t element_size;
  
  // При realloc указатели сбиваются, поэтому хорошо иметь
  // флаг запрещающий делать realloc после инициализации с 
  // определенным размером
  bool lock;
};

// Из пула можно выделять элементы, быстрее чем через malloc и
// уплотненно, так как нету никаких заголовков или 
// дополнительного выравнивания
void *pool_alloc(struct pool *self);

// Можно освобождать элементы, и при выделении новых (alloc)
// ячейки освобожденных элементов будут переиспользованы
void pool_free(struct pool *self, void *p);

// Можно брать индексы элементов в пуле, и хранить не указатели
// на элементы, а их индексы. Использование индексов вместо
// указателей, позволяет сократить использование памяти если
// указателей было много, uint32_t - 4 byte, pointer - 8 byte, 
// и в отличие от указателей, при realloc данных пула,  индексы
// будут указывать все на тот же объект
uint32_t pool_id(struct pool *self, void *p);

// Получение элемента по его индексу
void *pool_at(struct pool *self, uint32_t id);

Вместо привычных списков, использую интрузивные списки

struct list {
  struct list *prev;
  struct list *next;
  // Все, никаких данных он сам по себе не хранит!
};

// Функции для работы с ними довольно очевидные
...
void list_append(struct list *parent, struct list *child);
...

// А теперь к использованию, выдуманная структура игрока для
// примера
struct player {
  char name[128];
  uint32_t score;

  // Встраивается список таким способом 
  struct list lst;
};

// Теперь можно создать игроков A, B, C, и сделать из них
// список
struct list *players;

players = &A.lst;
list_append(players, &B.lst);
list_append(players, &C.lst);

// Имея указатель на список, и зная структуру на которой он 
// закреплен, легко получить указатель на структуру, достаточно
// вычесть адрес поля списка относительно самой структуры, для 
// этого обычно делается макрос
//
// struct player {
//   char name[128];  <-- 128 byte
//   uint32_t score;  <-- 4 byte
//   struct list lst; <-- 132
//                        player = (list_ptr - 132)
// };

У них есть ф-ция container_print(FILE* out, CONTAINER *c).

У меня обычно нету надобности в печати контейнера, смотрю через gdb состояния при отладки, ну и если нужно функции импорта/экспорта, но они не общие для всего.

PS Не совсем понял про какой слой

Я подумал все это делается ради того что бы не набирать дополнительно «%q\n», и мне это показалось неоправданным ради экономии пары символов, но теперь я вижу что цель была другая, обобщенное программирование, тут сразу вспоминаются такие проекты:

https://rurban.github.io/ctl/ - тоже попытка повторить шаблоны

https://libcello.org/ - попытка сделать С более высокоуровневым

Исходная версия MOPKOBKA, :

А вы как свои (или не свои) контейнеры организовываете?

Массив как структура включающая поля:

struct array {
  void *data;

  // size это то что обычно понимается под размером массива, 
  // если в массиве 5 элементов, то и size будет равен 5
  //
  // capacity это размер выделенной памяти, в пустом массиве 
  // это 0, в массиве до с 1..16 элементами capacity будет 16,
  // в массиве с 24 элементами 32, и далее так она и растет 
  // 0, 16, 32, 64, 128, 256, 512, ... Такой подход позволяет
  // не вызывать каждый раз realloc при добавлении элемента, а
  // только когда size достигает capacity, тогда capacity 
  // увеличивается в два раза, и можно черпать новые элементы
  //
  // Полезно заранее выставить capacity к примеру в 1000 
  // элементов, что бы обойтись в среднем парой вызовов realloc
  // на всю жизнь массива
  size_t capacity 
  size_t size;

  // Можно постоянно передавать размер элемента через аргументы
  // но мне это не нравится
  size_t element_size;
};

Еще часто использую pool элементов:

// Этот union не будет работать, но суть в том что
// освобожденные элементы образуют список из которого можно
// брать элементы для выделения. Вместо union просто type cast. 
union slot {
  uint32_t prev_free;
  char element[];
};

// Пул строится на массиве, а значит он так же поддерживает
// capacity который можно заранее установить
//
// Еще одно его преимущество для выделения множества объектов,
// это то, что пул можно убить одним free, вместе со всеми его 
// элементами, а не проходится по каждому элементу с free если
// каждый отдельный выделен через обычный malloc
struct pool {
  union slot *memory;
  size_t capacity;
  size_t size;
  size_t element_size;
  
  // При realloc указатели сбиваются, поэтому хорошо иметь
  // флаг запрещающий делать realloc после инициализации с 
  // определенным размером
  bool lock;
};

// Из пула можно выделять элементы, быстрее чем через malloc и
// уплотненно, так как нету никаких заголовков или 
// дополнительного выравнивания
void *pool_alloc(struct pool *self);

// Можно освобождать элементы, и при выделении новых (alloc)
// ячейки освобожденных элементов будут переиспользованы
void pool_free(struct pool *self, void *p);

// Можно брать индексы элементов в пуле, и хранить не указатели
// на элементы, а их индексы. Использование индексов вместо
// указателей, позволяет сократить использование памяти если
// указателей было много, uint32_t - 4 byte, pointer - 8 byte, 
// и в отличие от указателей, при realloc данных пула,  индексы
// будут указывать все на тот же объект
uint32_t pool_id(struct pool *self, void *p);

// Получение элемента по его индексу
void *pool_at(struct pool *self, uint32_t id);

Вместо привычных списков, использую интрузивные списки

struct list {
  struct list *prev;
  struct list *next;
  // Все, никаких данных он сам по себе не хранит!
};

// Функции для работы с ними довольно очевидные
...
void list_append(struct list *parent, struct list *child);
...

// А теперь к использованию, выдуманная структура игрока для
// примера
struct player {
  char name[128];
  uint32_t score;

  // Встраивается список таким способом 
  struct list lst;
};

// Теперь можно создать игроков A, B, C, и сделать из них
// список
struct list *players;

players = &A.lst;
list_append(players, &B.lst);
list_append(players, &C.lst);

// Имея указатель на список, и зная структуру на которой он 
// закреплен, легко получить указатель на структуру, достаточно
// вычесть адрес поля списка относительно самой структуры, для 
// этого обычно делается макрос
//
// struct player {
//   char name[128];  <-- 128 byte
//   uint32_t score;  <-- 4 byte
//   struct list lst; <-- 132
//                        player = (list_ptr - 132)
// };

PS Не совсем понял про какой слой

Я подумал все это делается ради того что бы не набирать дополнительно «%q\n», и мне это показалось неоправданным ради экономии пары символов, но теперь я вижу что цель была другая, обобщенное программирование, тут сразу вспоминаются такие проекты:

https://rurban.github.io/ctl/ - тоже попытка повторить шаблоны

https://libcello.org/ - попытка сделать С более высокоуровневым