I decided to develop my own horizon of writing my own implementation of a memory allocator under Linux , but I encountered the following problem when writing.

The call to sbrk() returns a pointer to the beginning of the allocated memory, then I bring this pointer to a pointer to the Hdr structure. But when I try to set the variable value by this pointer, the program crashes with a Segmentation error . The flair tells me that I’m doing something stupid, because the structure needs to be initialized before using it, but I don’t know how to do it properly in this situation, given that the memory for the structure is already allocated by calling sbrk() .

Thanks in advance for any help!

 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; ... } 
  • one
    @ user1496491 You should check if the function call was successful. And also if the address is appropriately aligned. - Vlad from Moscow
  • I’ll just quote from the smart book (talking about the brk()/sbrk() ) pair: "It’s even more practical to never use these procedures. A program that uses them cannot then also use malloc (), and it creates a big problem, since many elements of the standard library rely on the use of malloc (). Therefore, using brk () or sbrk () can lead to hard-to-find program crashes. " - PinkTux
  • Most likely you get a segfault somewhere else, even before the described actions, for example, when trying to print, when fopen (implicitly called during program initialization) refers to your malloc - avp
  • @avp SegFault appears exactly on the described line - checked with flags. - Yevgeny Latyshev
  • Not understood. In fact, such programs (when you change the standard allocator) can actually be debugged only under the debugger. And so, not enough information to advise something. - avp

2 answers 2

Let's see how sbrk() works. Schematically:

 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; } 

Consider from the end.

  1. If increment is 0 , then return the current address of the end of the heap.
  2. If negative, then reduce the heap and return the address of its new end.
  3. And if it is positive, then increase the heap and return its old value.

That's just point 3 mistakenly by many (not only novice programmers, but also book writers!) Is perceived as a way to allocate memory. Formally, yes, how would "memory be allocated," just the size of increment bytes. But in fact, this memory can not be used at its discretion directly ! Because trying to write something down there, you "knock down the settings" to the system memory manager, which naturally leads to the collapse of the program. Of course, in the case of a real program, and not in training examples, in which after using sbrk() and outputting some demo values ​​in the program, nothing happens.

PS By the way, if you were before reading dubious :) literature read man sbrk , then you would pay attention to the following:

On success, sbrk() returns the previous program break. (If the break has been increased, then this value has been increased). On error, (void *) -1 is returned , and errno is set to ENOMEM.

PPS I’m not even talking about replacing library malloc() my implementation - it’s not easy to shoot yourself in the foot, it’s sitting on a bunch of grenades, throwing gasoline over them and blowing them up.

  • Thanks for the advice, I'll go to redo it on mmap. Well, I replace the library malloc () in a virtual machine, so there can be no serious consequences. - Evgeny Latyshev
  • @YevgenyLatyshev, most likely you just can not start the program more difficult return 0; . Look at least for a start as a real malloc() looks inside, and how much of it he drags. - PinkTux
  • Well, everything is not so sad, a simple version with sbrk of a hundred lines seems to work. - avp

Something hooked me to the general distrust of the implementation of its malloc through sbrk.

Here is a very simple (essentially stupid), but working program, just to demonstrate the possibility of working with sbrk() .

Since the whole group of functions (malloc / calloc / realloc / free) is used by the library software together, it is impossible to implement malloc alone.

 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> 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(); } 

All the "oddities" of the write() code are associated with annoying (idiotic) warnings gcc / g ++ (g ++. Real (Ubuntu 5.4.0-6ubuntu1 ~ 16.04.4) 5.4.0) caused with optimization keys ( -O... ) when the write result is not used.

  • What causes explicit type conversions of the form (typeof(p)) in situations where the right-hand side is of type void * ? An argument popular in certain circles like “and I see a specific type right away” doesn’t work here, because from (typeof(p)) specific type does not become more visible :) So why are they here? Is that the desire to compile and gnuС ++ .. - AnT
  • @AnT, that's it - cursed crosses -) (from the same place #ifndef _GNU_SOURCE ). I never remember the tags in question and therefore compile with both. - avp Nov.