#linux #c #указатели #malloc #memory_management
Решил для развития кругозора написать собственную имплементацию аллокатора памяти
под Linux, но при написании столкнулся со следующей проблемой.
Вызов sbrk() возвращает указатель на начало выделенной памяти, затем я этот указатель
привожу к указателю на структуру Hdr. Но когда я пытаюсь задать значение переменной
по этому указателю, программа вылетает с Segmentation error. Чутьё подсказывает, что
я делаю глупость, ведь структуру нужно инициализировать прежде чем использовать, но
я не знаю как в данной ситуации правильно это сделать, учитывая, что память под структуру
уже выделена вызовом sbrk().
Заранее благодарен за любую помощь!
struct Hdr {
int is_avaible;
int size;
};
void *malloc(long memory_size) {
void *memory_location;
struct Hdr *current_location_struct;
...
memory_location = sbrk(memory_size);
if (memory_location == NULL)
return;
current_location_struct = (struct Hdr *)memory_location;
current_location_struct->is_available = 0;
...
}
Ответы
Ответ 1
Давайте посмотрим как работает sbrk(). Схематично:
void *sbrk(intptr_t increment)
{
void *heap_ptr = current_heap_end;
if( increment > 0 ) {
if( !expand_process_heap( increment ) ) {
heap_ptr = (void *)-1;
}
}
else if( increment < 0 ) {
heap_ptr = reduce_process_heap( -increment );
}
return heap_ptr;
}
Рассмотрим с конца.
Если increment равен 0, то возвращаем текущий адрес конца кучи.
Если отрицателен, то уменьшаем кучу и возвращаем адрес нового её конца.
И если он положителен, то увеличиваем кучу и возвращаем старое его значение.
Вот как раз пункт 3 ошибочно многими (не только начинающими программистами, но и
книгописателями!) воспринимается как некий способ выделить память. Формально - да,
как бы "память выделяется", как раз размером increment байтов. Но на деле эту память
использовать по своему усмотрению напрямую нельзя! Потому что пытаясь туда что-то записать
вы "сбиваете настройки" системному менеджеру памяти, что закономерно приводит к краху
программы. Конечно, в случае реальной программы, а не в учебных примерах, в которых
после использования sbrk() и вывода каких-то демо-значений в программе больше ничего
не происходит.
P.S. Кстати, если бы вы перед чтением сомнительной :) литературы прочитали man sbrk,
то обратили бы внимание на следующее:
On success, sbrk() returns the previous program break. (If the break was increased,
then this value is a pointer to the start of the newly allocated memory). On error,
(void *) -1 is returned, and errno is set to ENOMEM.
P.P.S. Я уж не говорю о том, что заменить библиотечный malloc() своей реализацией
- это не просто выстрелить себе в ногу, это усесться на связку гранат, облиться бензином
и подорвать их.
Ответ 2
Что-то меня зацепило всеобщее недоверие к реализации своего malloc через sbrk.
Вот очень простая (по сути дурацкая), но работающая программка, просто для демонстрации
возможности работы с sbrk().
Поскольку вся группа функций (malloc/calloc/realloc/free) используется библиотечным
софтом совместно, то реализовывать один лишь malloc нельзя.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include
#include
#include
#include
#include
static void *start;
void *
get_memstart ()
{
return start;
}
// size multiples 16, allocated to bound multiples 16
struct bhdr {
struct hdr {
size_t bsize;
uint cnt;
char is_free;
} hdr;
char pad[(16 - sizeof(struct hdr)) & 15];
};
// each malloc returns new sbrk block
void *
malloc (size_t size)
{
static uint cnt = 0;
const char *mtxt = "mmalloc\n";
if (write(1, mtxt, strlen(mtxt)))
cnt++;
if (!size)
return 0;
// increase space to mutilpe 16 for good bounding
struct bhdr *p = (typeof(p))sbrk((15 + size + sizeof(*p)) & ~15);
if ((long)p == -1L)
return 0;
if (!start)
start = p;
p->hdr.bsize = size;
p->hdr.cnt = cnt;
p->hdr.is_free = 0;
return p + 1;
}
void *
calloc (size_t nmemb, size_t size)
{
const char *mtxt = "mcalloc\n";
void *p = 0;
if (write(1, mtxt, strlen(mtxt))) {
p = malloc(nmemb * size);
if (p)
memset(p, 0, nmemb * size);
}
return p;
}
// mark early allocated block as free if bound is multiple 16
void
free (void *ptr)
{
const char *mtxt = "mfree\n";
if (write(1, mtxt, strlen(mtxt)))
if (!ptr || (long)ptr & 15)
return;
struct bhdr *p = (typeof(p))ptr;
p[-1].hdr.is_free = 1;
}
// if new size greater than old size returns copy in new block
void *
realloc (void *ptr, size_t size)
{
const char *mtxt = "mrealloc\n";
if (write(1, mtxt, strlen(mtxt)))
if (!ptr)
return malloc(size);
if ((long)ptr & 15)
return 0;
if (!size) {
free(ptr);
return (void *)-1L;
}
struct bhdr *p = (typeof(p))ptr;
size_t tsz = p[-1].hdr.bsize;
if (tsz >= size)
return ptr;
void *nptr = malloc(size);
if (nptr)
memcpy(nptr, ptr, tsz);
return nptr;
}
int
main (int ac, char *av[])
{
struct bhdr b;
printf("Hello, sbrk/malloc!\nbhdr: %d pad: %d hdr: %d\n",
(int)sizeof(b),
(int)sizeof(b.pad),
(int)sizeof(b.hdr));
char *s = 0;
size_t n;
errno = 0;
while ((fputs("Enter: ", stdout), getline(&s, &n, stdin)) > 0) {
printf("read %s", s);
if (*s == '.') {
puts("New line mem");
free(s);
s = 0;
}
}
perror("stdin");
return fcloseall();
}
Все "странности" кода с write() связаны с раздражающими (идиотскими) warnings gcc/g++
(g++.real (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0) вызываемым с ключами оптимизации (-O...)
когда результат write не используется.