Дополнительные примеры работы с указателями и выделением памяти
1. Выделение памяти с явным сохранением размера области.
Когда мы работаем с массивом, то необходимо хранить его размер. Для этого нужно постоянно носить с собой переменную, которую нужно передавать в функции или возвращать из функции. Способ реализации функции malloc не определён в документации языка, известно только, что в большинстве случаев выделенный участок памяти начинается с метаданных, которые хранят размер области. В различных компиляторах реализованы свои функции, которые могут разбирать метаданные и возвращать размер области памяти.
Реализуем функцию malloc, которая будет явно сохранять размер участка памяти и не будет зависеть от реализации компилятора. Для этого, в начале массива будет сохранять его размер в структуре _METADATA_.
typedef struct _METADATA_ {
size_t size;
} _METADATA_;
Структура содержит всего одно поле – размер size типа size_t. В будущем, если понадобится, без труда можно будет добавить новые поля. Теперь – функция my_malloc. Она получает размер участка памяти и возвращает указатель на выделенный участок.
1. Выделяем память. Её должно быть на sizeof(_METADATA_) байт больше, чтобы разместить заголовок.
void* ptr = malloc(size + sizeof(_METADATA_));
2. Если произошла ошибка и malloc вернула нулевой указатель, то выходим из функции
if (NULL == ptr) {
return NULL;
}
3. Теперь выделенную область кастуем до структуры _METADATA_ и записываем туда размер
((_METADATA_*) ptr)->size = size;
4. Возвращаем указатель со сдвигом на sizeof(_METADATA_), таким образом, он будет иметь размер size
return (char*) ptr + sizeof(_METADATA_);
(char*) нужно для того, чтобы прибавить к указателю неопределённого типа число байт, равное размеру _METADATA_.
void* my_malloc(size_t size) {
void* ptr = malloc(size + sizeof(_METADATA_));
if (NULL == ptr) {
return NULL;
}
((_METADATA_*) ptr)->size = size;
return (char*) ptr + sizeof(_METADATA_);
}
Функция my_free должна освобождать участок памяти, выделенный функцией my_malloc. Для этого необходимо сдвинуть указатель на размер структуры с метаданными.
void my_free(void* ptr) {
free((char*) ptr - sizeof(_METADATA_));
}
Функция get_size принимает указатель, полученный от функции my_malloc и читает размер массива из заголовка. Для этого сдвигаемся к началу области, кастуем её до _METADATA_ и обращаемся к полю size
size_t get_size(void* ptr) {
return ((_METADATA_*)((char*) ptr - sizeof(_METADATA_)))->size;
}
Функция realloc похожа на malloc. Выделяем память под новый массив размера большего на sizeof(_METADATA_). Заносим в заголовок новый размер.
void* my_realloc(void* xptr, size_t new_size) {
void* ptr = realloc((char*) xptr - sizeof(_METADATA_), new_size);
if (ptr == NULL) {
return NULL;
}
((_METADATA_*) ptr)->size = new_size;
return (char*) ptr + sizeof(_METADATA_);
}
Проблемы возникают с функцией calloc. Так как она принимает размер массива и размер его элементов, то размер нашего заголовка может быть не кратен размеру элемента. Будет выделять память функцией malloc, а затем функцией memset заполнять выделенную область нулями.
void* my_calloc(size_t size, size_t num) {
size_t real_size = (size * num) + sizeof(_METADATA_);
void* ptr = malloc(real_size);
memset((char*)ptr + sizeof(_METADATA_), 0, size*num);
((_METADATA_*) ptr)->size = size;
return (char*)ptr + sizeof(_METADATA_);
}
Теперь прикроем стандартные функции макросами
#define malloc my_malloc #define free my_free #define realloc my_realloc #define calloc my_calloc
Соберём всё вместе с примерами
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _METADATA_ {
size_t size;
} _METADATA_;
void* my_malloc(size_t size) {
void* ptr = malloc(size + sizeof(_METADATA_));
if (NULL == ptr) {
return NULL;
}
((_METADATA_*) ptr)->size = size;
return (char*) ptr + sizeof(_METADATA_);
}
void* my_calloc(size_t size, size_t num) {
size_t real_size = (size * num) + sizeof(_METADATA_);
void* ptr = malloc(real_size);
memset((char*)ptr + sizeof(_METADATA_), 0, size*num);
((_METADATA_*) ptr)->size = size;
return (char*)ptr + sizeof(_METADATA_);
}
size_t get_size(void* ptr) {
return ((_METADATA_*)((char*) ptr - sizeof(_METADATA_)))->size;
}
void* my_realloc(void* xptr, size_t new_size) {
void* ptr = realloc((char*) xptr - sizeof(_METADATA_), new_size);
if (ptr == NULL) {
return NULL;
}
((_METADATA_*) ptr)->size = new_size;
return (char*) ptr + sizeof(_METADATA_);
}
void my_free(void* ptr) {
free((char*) ptr - sizeof(_METADATA_));
}
#define malloc my_malloc
#define free my_free
#define realloc my_realloc
#define calloc my_calloc
void printIntArray(int *a) {
int size = get_size(a) / sizeof(int);
int i;
for (i = 0; i < size; i++) {
printf("%d ", a[i]);
}
}
int** ones(int num) {
int** a = (int**) malloc(num * sizeof(int*));
int i;
for (i = 0; i < num; i++) {
a[i] = (int*) calloc(num, sizeof(int));
a[i][i] = 1;
}
return a;
}
void main() {
int* a = (int*) malloc(10*sizeof(int));
int** one = NULL;
int i, j;
for (i = 0; i < 10; i++) {
a[i] = i*i;
}
printf("size of a = %d byte\n", get_size(a));
printIntArray(a);
free(a);
getch();
printf("\n");
one = ones(10);
for (i = 0; i < 10; i++) {
for (j = 0; j < 10; j++) {
printf("%d ", one[i][j]);
}
printf("\n");
}
for (i = 0; i < 10; i++) {
free(one[i]);
}
free(one);
getch();
}
2. Простой пул памяти.
Уже оговаривалось ранее, что частое выделение памяти под множество мелких объектов, а также изменение размера выделенной области памяти замедляет работу и приводит к фрагментации памяти. Создадим просто пул памяти – заранее зарезервированную область памяти, которую будем раздавать. Для простоты сделаем пул глобальной переменной.
void** mempool = NULL; int *checkbox; size_t msize;
Здесь mempool – это наш пул, двумерный массив. msize – число строк, соответствует числу блоков памяти, размер которых задаётся пользователем при создании, checkbox – массива флагов. Длина массива флагов msize. Если i-й флаг поднят, то i-й блок памяти выделен.
Как это работает:
- 1) Создаём пул
- 2) При запросе памяти проходим по массиву checkbox и находим первый ненулевой сегмент. Возвращаем этот сегмент и помечаем в checkbox, что сегмент был выделен.
- 3) При очистке памяти проходим по массиву mempool и сравниваем указатели. Если они равны, то в соответствующую ячейку checkbox заносим ноль.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
void** mempool = NULL;
int *checkbox;
static size_t msize;
void prepare_pool(size_t items, size_t len) {
size_t i;
msize = items;
checkbox = (int*) calloc(len, sizeof(int));
mempool = (void**) malloc(msize*sizeof(void*));
for (i = 0; i < msize; i++) {
mempool[i] = malloc(len);
}
}
void freemempool() {
for (;msize != 0; msize--) {
free(mempool[msize-1]);
}
free(mempool);
}
void* mylloc() {
size_t i;
for (i = 0; i < msize; i++) {
if (!checkbox[i]) {
checkbox[i] = 1;
return mempool[i];
}
}
return NULL;
}
void myfree(void *ptr) {
size_t i;
for (i = 0; i < msize; i++) {
if (mempool[i] == ptr) {
checkbox[i] = 0;
break;
}
}
}
void main () {
int i;
char *a, *b, *c;
for (i = 0; i < 1000000; i++) {
a = (char*) malloc(100);
b = (char*) malloc(100);
c = (char*) malloc(100);
free(a);
free(b);
free(c);
}
/*раскомментируйте и закомментируйте старый цикл
prepare_pool(10, 1000);
for (i = 0; i < 1000000; i++) {
a = (char*) mylloc();
b = (char*) mylloc();
c = (char*) mylloc();
myfree(a);
myfree(b);
myfree(c);
}
freemempool();
*/
}
