-1

This code segfaults on line 97 (according to gdb) on one machine (Linode) yet runs just fine on a different machine (personal) and I haven't really been able to figure out why. I tried ensuring that the heap was extended properly via sbrk but that still didn't seem to fix the issue. If anyone wouldn't mind explaining what I did wrong, I'd really appreciate it.

`

#define _DEFAULT_SOURCE
#define _BSD_SOURCE

#include <stdio.h>
#include <math.h>

typedef struct block        // This is a node in a linked list representing various sections of memory
{
    int size;               // size of the block of allocated memory
    int free;               // whether the block is free and can be reallocated
    struct block* next;     // pointer to the next block in the linked list
    char end[1];            // end represents the end of the header block struct
} block_t;

#define STRUCT_SIZE sizeof(block_t)

// --- Global variables
block_t* head = NULL;                           // Pointer to head of the linked list
block_t* lastVisited = NULL;                    // Pointer to the last visited node

void* brkPoint = NULL;                         // This is a pointer to the empty space on heap

// findBlock: Iterates through all blocks of memory until it is able to return a block able to contain a node of size size.
//      headptr: Head of the linked list of blocks
//      size: Size of the memory section being claimed
block_t* findBlock(block_t* headptr, unsigned int size) {
    block_t* ptr = headptr;

    while (ptr != NULL) {
        if (ptr->size >= (size + STRUCT_SIZE) && ptr->free == 1) {
            return ptr;
        }
        lastVisited = ptr;
        ptr = ptr->next;
    }
    return ptr;
}


// splitBlock: Given a block ptr, split it into two blocks of size of size and ptr->size - size
//      ptr: Pointer to the block being split
//      size: Size of the first one of the two blocks
void splitBlock(block_t* ptr, unsigned int size) {
    block_t* newBlock;

    newBlock = ptr->end + size;
    newBlock->size = ptr->size - size - STRUCT_SIZE;
    newBlock->free = 1;
    newBlock->next = ptr->next;

    ptr->size = size;
    ptr->free = 0;
    ptr->next = newBlock;
}


// Increase amount of memory the program uses from the heap
//      lastVisitedPtr: Pointer to the beginning of free heap (end of the program heap)
//      size: The amount that you want to increase
block_t* increaseAllocation(block_t* lastVisitedPtr, unsigned int size) {
    brkPoint = sbrk(0);
    block_t* curBreak = brkPoint;       //Current breakpoint of the heap

    if (sbrk(size + STRUCT_SIZE) == (void*)-1) {
        return NULL;
    }

    curBreak->size = (size + STRUCT_SIZE) - STRUCT_SIZE;
    curBreak->free = 0;
    curBreak->next = NULL;
    lastVisitedPtr->next = curBreak;

    if (curBreak->size > size)
        splitBlock(curBreak, size);

    return curBreak;
}

// malloc: A custom implementation of malloc, behaves exactly as expected
//      _size: the amount of memory to be allocated into a block
//      returns void*, a pointer to the block
void* mymalloc(size_t _size) {
    void* brkPoint1;                        // New heap breakpoint
    unsigned int size = _size;
    int memoryNeed =  size + STRUCT_SIZE;   // Total size needed, including metadata
    block_t* ptr;                           // ptr to new block

    brkPoint = sbrk(0);                     // Set breakpoint to heap

    if (head == NULL) {                     // If being run for the first time
        if (sbrk(memoryNeed) == (void*)-1) { // If you cannot allocate enough memory, return null
            return NULL;
        }

        brkPoint1 = sbrk(0);                // Set new breakpoint to heap
        head = brkPoint;                    // Head is at heap
        head->size = memoryNeed - STRUCT_SIZE;
        head->free = 0;                     // Head is no longer free
        head->next = NULL;                  // No next

        ptr = head;                         // Return pointer is head 

        printf("Malloc %zu bytes\n", size);
        return ptr->end;                    // Return end of the metadata pointer (AKA beginning of allocated memory)
    }

    else {                                  // Head exists
        block_t* freeBlock = NULL;          
        freeBlock = findBlock(head, size);  // Find a block that can fit size

        if (freeBlock == NULL) {             
            freeBlock = increaseAllocation(lastVisited, size);  // Increase heap and create new block
            if (freeBlock == NULL) {
                return NULL;
            }
            printf("Malloc %zu bytes\n", size);
            return freeBlock->end;
        }

        else {                              // Free block with size > _size exists, claim it
            if (freeBlock->size > size) {   // If block's size is > size, split it down to size
                splitBlock(freeBlock, size);
            }
        }
        printf("Malloc %zu bytes\n", size);
        return freeBlock->end;
    }
}

// myfree: Sets block referenced by pointer to be free and merges consecutive blocks
void myfree(void* ptr) {

    block_t* toFree;
    toFree = ptr - STRUCT_SIZE;

    if (toFree >= head && toFree <= brkPoint) {
        toFree->free = 1;
        printf("Freed %zu bytes\n", toFree->size);
    }
}


#define ARRAY_ELEMENTS 1024

int main() {

  // Allocate some data
  int *data = (int *) mymalloc(ARRAY_ELEMENTS * sizeof(int));

  // Do something with the data
  int i = 0;
  for (i = 0; i < ARRAY_ELEMENTS; i++) {
    data[i] = i;
  }

  // Free the data
  myfree(data);

  return 0;
}

`

As mentioned above, I tried debugging with gdb and expanding the heap with sbrk, but that didn't fix the issue. I have no idea why it would run fine on my personal machine but not on a machine hosted elsewhere. Thanks a lot for checking this out

  • Your code does not contain any type declarations or `main`. Please provide a [MCVE](https://stackoverflow.com/help/minimal-reproducible-example). – Gerhardh Nov 30 '22 at 08:51
  • @Gerhardh should have an example that compiles now – Malik Allahham Nov 30 '22 at 09:02
  • You should use `sizeof` instead of hard-coding the struct size, due to structure padding, and since the member sizes may vary from system to system. – rmn Nov 30 '22 at 09:14
  • @rmn I've tried that before. However, that did not seem to fix the issue :/ thank you for the suggestion tho – Malik Allahham Nov 30 '22 at 09:16
  • The line `toFree = ptr - STRUCT_SIZE;` prevents my system from compiling your code. You cannot do pointer artithmetic on a `void*` pointer because the pointed-to type is incomplete and of unknown size. What compiler(s) are you using that accept such syntax? – Adrian Mole Nov 30 '22 at 09:45
  • ... OK, I see that it is a GNU extension. But it may still be causing either undefined or different behaviour on your two systems. Try casting `ptr` to a `char*` before the adjustment, and see if that makes a difference. – Adrian Mole Nov 30 '22 at 09:55
  • @AdrianMole No cigar :/ gdb really isn't helping too much with this one I'll be honest, but I'm likely just bad at using it. – Malik Allahham Nov 30 '22 at 10:05
  • There have been several edits since the initial post noting an error on "line 89". Which line is it currently? – Mark Plotnick Nov 30 '22 at 17:56
  • @MarkPlotnick Line 97 now, sorry. – Malik Allahham Nov 30 '22 at 18:24
  • There is a ton of warnings which you should fix. This one in particular is likely to cause crashes: `t.c:61:16: warning: implicit declaration of function ‘sbrk’`. – Employed Russian Dec 01 '22 at 01:42
  • When I add missing `#include `, the crash goes away. – Employed Russian Dec 01 '22 at 01:44
  • @EmployedRussian that was the solution! I guess the compiler on one system implicitly linked it whereas the other system didn't? Anyways, thank you so so much! I very much appreciate you (and everyone else) looking into this for me :) – Malik Allahham Dec 01 '22 at 03:13
  • @MalikAllahham I've added an answer. Linking has nothing to do with it. – Employed Russian Dec 01 '22 at 04:27

1 Answers1

2

There is a ton of warnings which you should fix.

This one in particular is likely to cause crashes:

t.c:61:16: warning: implicit declaration of function ‘sbrk’.

Why is this likely to cause a crash?

Without a prototype, the C compiler is required (by the standard) to assume that the function returns an int.

On 32-bit platforms, this typically doesn't cause a problem because sizeof(int) == sizeof(void*) == 4.

But on 64-bit platforms sizeof(int) == 4 and sizeof(void*) == 8. Thus assigning void *p = sbrk(0); without a prototype may result in the pointer having only the low 4 bytes of the returned address; and that is likely to produce a crash when that pointer is dereferenced.

When I add missing #include <unistd.h> (where the prototype for sbrk is), the crash goes away.


In general you should always compile with -Wall -Wextra and fix resulting warnings. The compiler will often tell you about bugs, and save you a lot of debugging time.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362