Страницы

Поиск по вопросам

понедельник, 6 января 2020 г.

Segmentation fault при обращении к указателю

#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 не используется.

Комментариев нет:

Отправить комментарий