История изменений
Исправление 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/ - попытка сделать С более высокоуровневым