Needing advice for implementing malloc and free in C - c

For school, I need to write a program that uses my own implementation of malloc and free. I need to be able to report on all the chunks of memory in my 'heap', whether it's allocated or not. I feel like I've written good code to do so, but evidently not. The first few times I ran it, the report kept reporting on the same address forever. While trying to debug that, there came a point that the program wouldn't even let me start allocate space to use as my 'heap', it would just get a segmentation fault and quit. Any pointers on where I'm going wrong, or even to clean up my code at all, would be super helpful.
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#define WORDSIZE 8
#define ALLOCMAGIC 0xbaddecaf
#define FREEMAGIC 0xdeadbeef
typedef struct __header_t {
size_t size;
int magic;
} header_t;
typedef struct __node_t {
size_t size;
struct __node_t *next;
} node_t;
node_t *head = NULL;
// Find the free node that occurs just before the given node.
node_t *findLastFree(node_t * node) {
// Initialize some pointers to traverse the free node linked list;
node_t *lastFree = head;
node_t *nextFree = lastFree->next;
// Traverse linked list until the last node's pointer is pointed to NULL,
// meaning the end of the list.
while (nextFree != NULL) {
// Check if target node is less than the next node, meaning the target node
// is between last and next. If so, then return last node.
if (node < nextFree) {
return lastFree;
}
lastFree = nextFree;
nextFree = lastFree->next;
}
// If we have reached the end of the list and the target node is still greater
// than the last node, return last node.
return lastFree;
}
// If the given pointer is allocated, deallocate the space and coalesce the free
// node list.
void myFree(void *ptr) {
// Initialize some free node pointers and assert that the given pointer is
// the beginning of allocated space.
node_t *lastFree;
node_t *nextFree;
node_t *newFree;
header_t *block = ((header_t *) ptr) - 1;
assert(block->magic == ALLOCMAGIC);
// Set this block's signal to free space
block->magic = FREEMAGIC;
// Typecast the block into a free node and set it's size.
size_t size = block->size + sizeof(header_t);
newFree = (node_t *) block;
newFree->size = size;
// Check if node is before the first free node. If so set the new node as
// the new head. If not, then handle node as it occurs after head.
if (newFree < head) {
nextFree = head;
// Check if new node ends at the start of head. If so, merge them
// into a single free node. Else point the new node at the previous head.
// Either way, set new free as the new head.
if ((newFree + newFree->size) == head) {
newFree->next = head->next;
newFree->size = newFree->size + head->size;
} else {
newFree->next = head;
}
head = newFree;
} else {
// Set the free nodes for before and after the new free node.
lastFree = findLastFree(newFree);
nextFree = lastFree->next;
// Check if new node is the last node. If so, point the previous final
// node at the new node and point the new node at NULL.
if (nextFree == NULL) {
lastFree->next = newFree;
newFree->next = NULL;
}
// Check if end of new node is touching next node. If so, merge them
// into a single free node. Else point new free and next free.
if ((newFree + newFree->size) == nextFree) {
newFree->next = nextFree->next;
newFree->size = newFree->size + nextFree->size;
} else {
newFree->next = nextFree;
}
// Check if start of new node is touching last free node. If so, merge
// them into a single free node. Else point last's next to new free.
if ((lastFree + lastFree->size) == newFree) {
lastFree->next = newFree->next;
lastFree->size = lastFree->size + newFree->size;
} else {
lastFree->next = newFree;
}
}
}
// Split the given free node to fit the given size. Create a new node at the
// remainder and rearrange the free list to accomodate.
void splitBlock(node_t *node, size_t size) {
// Create a new pointer at the end of the requested space.
void *newBlock = node + size;
// Set the bits of the new space as if it were allocated then freed.
header_t *hptr = (header_t *) newBlock;
hptr->size = (node->size - size - sizeof(header_t));
hptr->magic = FREEMAGIC;
// Typecast the new space into a node pointer. Reinsert it into the free
// node list.
node_t *newFree = (node_t *) newBlock;
newFree->size = node->size - size;
newFree->next = node->next;
node_t *lastFree = findLastFree(newFree);
lastFree->next = newFree;
}
// Find a free node that can fit the given size. Split the node so no space is
// wasted. If no node can fit requested size, increase the heap size to accomodate.
void *findFirstFit(size_t size) {
// Create a node pointer to traverse the free node list.
node_t *node = head;
// Traverse the list until the end is reached.
while(node != NULL) {
// Check if the node can accomodate the requested size.
if (node->size >= size) {
// Split the current node at the requested size and return a pointer
// to the start of the requested space.
splitBlock(node, size);
return (void *) node;
}
node = node->next;
}
// No free space could fit requested size, so request more space at the end
// of the heap.
void *newspace = sbrk(size);
assert(newspace >= 0);
return newspace;
}
// Allocate a block of space for the given size and return a pointer to the start
// of the freed space.
void *myMalloc(size_t need) {
// Round the given size up to the next word size. Add the size of a header to
// the amount actually needed to allocate.
need = (need + WORDSIZE - 1) & ~(WORDSIZE - 1);
size_t actual = need + sizeof(header_t);
// Find a free node that can accomodate the given size. Check it is valid.
void *firstfit = findFirstFit(actual);
assert(firstfit >= 0);
// Create a header for the newly allocated space.
header_t *hptr = (header_t *) firstfit;
hptr->magic = ALLOCMAGIC;
hptr->size = need;
return (void *) (hptr + 1);
}
// Print a report on the space starting at the given pointer. Return a pointer to
// the start of the next block of space.
void *reportAndGetNext(void *ptr) {
void *nextptr;
header_t *hptr = (header_t *) ptr;
// Check if the pointer is pointing to allocated space.
if (hptr->magic == ALLOCMAGIC) {
// Report the characteristics of the current block.
printf("%p is ALLOCATED starting at %p and is %zd bytes long.\n", hptr, (hptr + 1), hptr->size);
// Set the next pointer to be returned.
nextptr = hptr + hptr->size + sizeof(header_t);
} else {
// Cast the pointer as a free node. Set the next pointer to be returned.
node_t *free = (node_t *) ptr;
nextptr = free + free->size;
// Report the characteristics of the current block.
printf("%p is FREE for %zd bytes.\n", hptr, free->size);
}
return nextptr;
}
// Report on all blocks of space contained within the heap space, starting at the
// given pointer.
void report(void* startheap) {
void *ptr = startheap;
void *end = sbrk(0);
int count = 50;
printf("Current Status of Heap:\n");
while (ptr != NULL && count > 0) {
ptr = reportAndGetNext(ptr);
count = count - 1;
}
printf("Heap Length: %zd \n", (end - startheap));
}
int main(void) {
void *start = sbrk(4096);
assert(start >= 0);
head = (node_t *) start;
head->size = 4096;
head->next = NULL;
printf("Allocating block 1");
void *ptr1 = myMalloc(26);
void *ptr2 = myMalloc(126);
report(start);
myFree(ptr1);
myFree(ptr2);
return 0;
}

The first obvious error I see is with pointer arithmentic. SplitBlock is trying to split size bytes from the front of a block, but when you do:
void splitBlock(node_t *node, size_t size) {
// Create a new pointer at the end of the requested space.
void *newBlock = node + size;
Your newBlock pointer is actually size * sizof(node_t) bytes into the block -- which may well be past the end of the block. You need to cast node to a char * before doing pointer arithmetic with it if you want byte offsets. However, you may then run into alignment issues...

Related

Howto manage freeing single pointers from a double-pointer block

I have a block of pointers to some structs which I want to handle (i.e. free) separately. As an example below there is an integer double-pointer which should keep other pointers to integer. I then would like to free the second of those integer pointers (in my program based on some filterings and calculations). If I do so however, I should keep track of int-pointers already set free so that when I iterate over the pointers in the double-pointer I do not take the risk of working with them further. Is there a better approach for solving this problem (in ANSI-C) without using other libs (e.g. glib or alike)?
Here is a small simulation of the problem:
#include <stdio.h>
#include <stdlib.h>
int main() {
int **ipp=NULL;
for (int i = 0; i < 3; i++) {
int *ip = malloc(sizeof (int));
printf("%p -> ip %d\n", ip, i);
*ip = i * 10;
if ((ipp = realloc(ipp, sizeof (int *) * (i + 1)))) {
ipp[i] = ip;
}
}
printf("%p -> ipp\n", ipp);
for (int i = 0; i < 3; i++) {
printf("%d. %p %p %d\n", i, ipp + i, *(ipp+i), **(ipp + i));
}
// free the middle integer pointer
free(*(ipp+1));
printf("====\n");
for (int i = 0; i < 3; i++) {
printf("%d. %p %p %d\n", i, ipp + i, *(ipp+i), **(ipp + i));
}
return 0;
}
which prints something like
0x555bcc07f2a0 -> ip 0
0x555bcc07f6f0 -> ip 1
0x555bcc07f710 -> ip 2
0x555bcc07f6d0 -> ipp
0. 0x555bcc07f6d0 0x555bcc07f2a0 0
1. 0x555bcc07f6d8 0x555bcc07f6f0 10
2. 0x555bcc07f6e0 0x555bcc07f710 20
====
0. 0x555bcc07f6d0 0x555bcc07f2a0 0
1. 0x555bcc07f6d8 0x555bcc07f6f0 0
2. 0x555bcc07f6e0 0x555bcc07f710 20
Here I have freed the middle int-pointer. In my actual program I create a new block for an integer double-pointer, iterate over the current one, create new integer pointers and copy the old values into it, realloc the double-pointer block and append the new pointer to it, and at the end free the old block and all it's containing pointers. This is a bit ugly, and resource-consuming if there is a huge amount of data, since I have to iterate over and create and copy all the data twice. Any help is appreciated.
Re:
"This is a bit ugly, and resource-consuming if there is a huge amount of data, since I have to iterate over and create and copy all the data
twice. Any help is appreciated."
First observation: It is not necessary to use realloc() when allocating new memory on a pointer that has already been freed. realloc() is useful when needing to preserve the contents in a particular area of memory, while expanding its size. If that is not a need (which is not in this case) malloc() or calloc() are sufficient. #Marco's suggestion is correct.
Second observation: the following code snippet:
if ((ipp = realloc(ipp, sizeof (int *) * (i + 1)))) {
ipp[i] = ip;
}
is a potential memory leak. If the call to realloc()_ fails, the pointer ipp will be set to null, making the memory location that was previously allocated becomes orphaned, with no way to free it.
Third observation: Your approach is described as needing:
Array of struct
dynamic memory allocation of a 2D array
need to delete elements of 2D array, and ensure they are not referenced once deleted
need to repurpose deleted elements of 2D array
Your initial reaction in comments to considering using an alternative approach notwithstanding, Linked lists are a perfect fit to address the needs stated in your post.
The fundamental element of a Linked List uses a struct
Nodes (elements) of a List are dynamically allocated when created.
Nodes of a List are not accessible to be used once deleted. (No need to track)
Once the need exists, a new node is easily created.
Example struct follows. I like to use a data struct to contain the payload, then use an additional struct as the conveyance, to carry the data when building a Linked List:
typedef struct {//to simulate your struct
int dNum;
char unique_name[30];
double fNum;
} data_s;
typedef struct Node {//conveyance of payload, forward and backward searchable
data_s data;
struct Node *next; // Pointer to next node in DLL
struct Node *prev; // Pointer to previous node in DLL
} list_t;
Creating a list is done by creating a series of nodes as needed during run-time. Typically as records of a database, or lines of a file are read, and the elements of the table record (of element of the line in a file) are read into and instance of the data part of the list_s struct. A function is typically defined to do this, for example
void insert_node(list_s **head, data_s *new)
{
list_s *temp = malloc(sizeof(*temp));
//insert lines to populate
temp.data.dNum = new.dNum;
strcpy(temp.data.unique_name, new.unique_name);
temp.fNum = new.fNum
//arrange list to accomdate new node in new list
temp->next = temp->prev = NULL;
if (!(*head))
(*head) = temp;
else//...or existing list
{
temp->next = *head;
(*head)->prev = temp;
(*head) = temp;
}
}
Deleting a node can be done in multiple ways. It the following example method a unique value of a node member is used, in this case unique_name
void delete_node_by_name(list_s** head_ref, const char *name)
{
BOOL not_found = TRUE;
// if list is empty
if ((*head_ref) == NULL)
return;
list_s *current = *head_ref;
list_s *next = NULL;
// traverse the list up to the end
while (current != NULL && not_found)
{
// if 'name' in node...
if (strcmp(current->data.unique_name, name) == 0)
{
//set loop to exit
not_found = FALSE;
//save current's next node in the pointer 'next' /
next = current->next;
// delete the node pointed to by 'current'
delete_node(head_ref, current);
// reset the pointers
current = next;
}
// increment to next node
else
{
current = current->next;
}
}
}
Where delete_node() is defined as:
void delete_node(list_t **head_ref, list_t *del)
{
// base case
if (*head_ref == NULL || del == NULL)
return;
// If node to be deleted is head node
if (*head_ref == del)
*head_ref = del->next;
// Change next only if node to be deleted is NOT the last node
if (del->next != NULL)
del->next->prev = del->prev;
// Change prev only if node to be deleted is NOT the first node
if (del->prev != NULL)
del->prev->next = del->next;
// Finally, free the memory occupied by del
free(del);
}
This link is an introduction to Linked Lists, and has additional links to other related topic to expand the types of lists that are available.
You could use standard function memmove and then call realloc. For example
Let's assume that currently there are n pointers. Then you can write
free( *(ipp + i ) );
memmove( ipp + i, ipp + i + 1, ( n - i - 1 ) * sizeof( *pp ) );
*( ipp + n - 1 ) = NULL; // if the call of realloc will not be successfull
// then the pointer will be equal to NULL
int **tmp = realloc( ipp, ( n - 1 ) * sizeof( *tmp ) );
if ( tmp != NULL )
{
ipp = tmp;
--n;
}
else
{
// some other actions
}

C - Check if index of struct array is uninitialized

I'm making a HashMap in C but am having trouble detecting when a Node has been initialized or not.
Excerpts from my code below:
static struct Node
{
void *key, *value;
struct Node *next;
};
struct Node **table;
int capacity = 4;
table = malloc(capacity * sizeof(struct Node));
// At this point I should have a pointer to an empty Node array of size 4.
if (table[0] != NULL)
{
// This passes
}
I don't see what I can do here. I've read tons of other posts of this nature and none of their solutions make any sense to me.
malloc does not initialize the memory allocated. You can use calloc to zero-initialize the memory.
// Not sizeof(struct Node)
// table = calloc(capacity, sizeof(struct Node));
table = calloc(capacity, sizeof(*table));
After that, it will make sense to use:
if (table[0] != NULL)
{
...
}
I suggest you consider something like a HashMapCollection type that you create with a set of functions to handle the various memory operations you need.
So you might have code something like the following. I have not tested this nor even compiled it however it is a starting place.
The FreeHashMapCollection() function below would process a HashMapCollection to free up what it contains before freeing up the management data structure. This may not be what you want to do so that is something for you to consider.
The idea of the following is to have a single pointer for the HashMapCollection struct and the array or list of HashMapNode structs immediately follows the management data so a single free() would free up everything at once.
typedef struct _TAGHashMapNode {
void *key, *value;
struct _TAGHashMapNode *next;
} HashMapNode;
typedef struct {
int iCapacity; // max number of items
int iSize; // current number of items
HashMapNode *table; // pointer to the HashMapNode table
} HashMapCollection;
Then have a function to allocate a HashMapCollection of a particular capacity initialized properly.
HashMapCollection *AllocateHashMapCollection (int iCapacity)
{
HashMapCollection *p = malloc (sizeof(HashMapCollection) + iCapacity * sizeof(HashMapNode));
if (p) {
p->table = (HashMapNode *)(p + 1);
p->iCapacity = iCapacity;
p->iSize = 0;
memset (p->table, 0, sizeof(HashMapNode) * iCapacity);
}
return p;
}
HashMapCollection *ReallocHashMapCollection (HashMapCollection *p, int iNewCapacity)
{
HashMapCollection *pNew = realloc (p, sizeof(HashMapCollection) + sizeof(HashMapNode) * iNewCapacity);
if (pNew) {
pNew->table = (HashMapNode *)(pNew + 1);
if (p == NULL) {
// if p is not NULL then pNew will have a copy of that.
// if p is NULL then this is basically a malloc() so initialize pNew data.
pNew->iCapacity = pNew->iSize = 0;
}
if (iNewCapacity > pNew->iCapacity) {
// added more memory so need to zero out that memory.
memset (pNew->table + iCapacity, 0, sizeof(HashMapNode) * (iNewCapacity - pNew->iCapacity));
}
pNew->iCapacity = iNewCapacity; // set our new current capacity
p = pNew; // lets return our new memory allocated.
}
return p; // return either old pointer if realloc() failed or new pointer
}
void FreeHashMapCollection (HashMapCollection *p)
{
// go through the list of HashMapNode items and free up each pair then
// free up the HashMapCollection itself.
for (iIndex = 0; iIndex < p->iCapacity; iIndex++) {
if (p->table[iIndex].key) free (p->table[iIndex].key);
if (p->table[iIndex].value) free (p->table[iIndex].value);
// WARNING ***
// if these next pointers are actually pointers inside the array of HashMapNode items
// then you would not do this free as it is unnecessary.
// this free is only necessary if next points to some memory area
// other than the HashMapNode table of HashMapCollection.
if (p->table[iIndex].next) free (p->table[iIndex].next);
// even though we are going to free this, init to NULL
p->table[iIndex].key = NULL;
p->table[iIndex].value = NULL;
p->table[iIndex].next = NULL;
}
free (p); // free up the memory of the HashMapCollection
}

Heap alloc in c

#include <stdio.h>
#include <malloc.h>
typedef struct Node {
int value; //4
struct Node* next; //4
}Node;
Node *create();
void add();
void del();
void search();
Node *create(int v) {
Node *first;
first = (Node *)(calloc(1,sizeof(*first)));
first->value = v;
first->next = NULL;
return first;
}
void add(Node **head,int v) {
Node *p;
p = (Node *)(calloc(1,sizeof(*p)));
p->value = v;
p->next = *head;
*head = p;
}
void search(Node *head) {
Node *p;
p=head;
while(p != NULL) {
printf("address is %d;value address is %d;next address is %d;next content is %d\n",p,&(p->value),&(p->next),p->next);
p = p->next;
}
}
int main() {
Node *head;
head = create(0);
add(&head,1);
add(&head,2);
add(&head,3);
search(head);
}
sizeof(Node) == 8, but why is every node's size in the heap is 16 bytes? thinks
(my system is 32bit).
struct node is 4bytes + 4bytes = 8bytes.
The nodes sizes aren't 16 bytes, it's just that malloc() chooses to skip 8 bytes of memory for some reason, likely for its own bookkeeping. If you want to conserve memory, do few large allocations, not many small ones, or else the bookkeeping overhead can cost quite a lot.
well, even if the memory allocated between calls to calloc() was continuous for you program (which you cannot make sure), don't forget that the lib c has 'private' data stored in the hunk of memory you allocated.
usually there is a header like:
struct hdr
{
size_t size; /* Exact size requested by user. */
unsigned long int magic; /* Magic number to check header integrity. */
struct hdr *prev;
struct hdr *next;
__ptr_t block; /* Real block allocated, for memalign. */
unsigned long int magic2; /* Extra, keeps us doubleword aligned. */
};
(code from)
You may see that the block actually the buffer of data that you'll get when calling malloc()/calloc(), is surrounded by a lot of extra data (ok, here is special case for debug, thus there are probably extra magics).
The errors involved in your code were logic errors related to the various list functions. When you have a create function, that functions job is to allocate memory for the node and assign any values required. It does not worry about which node it is dealing with.
Conversely, your add function does NOT allocate anything, it simply calls create to handle that work and then its job is merely properly wiring pointers and next->pointers to the proper node.
Since you are dealing with a head node that contains data, you have 3 possible conditions for add; (1) when head is NULL; (2) when head->next is NULL; and (3) all remaining additions.
Putting those pieces together and adding a print function, your code could look like the following:
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int value; //4
struct Node* next; //4
} Node;
/* function prototypes */
Node *create (int v);
void add (Node **head, int v);
void del ();
void search (Node *head);
void printvalues (Node *head);
int main (void) {
Node *head = NULL;
// head = create(0);
add (&head,0);
add (&head,1);
add (&head,2);
add (&head,3);
printf ("\nsearching:\n\n");
search (head);
printf ("\nprinting:\n\n");
printvalues (head);
return 0;
}
/* create - only creates nodes */
Node *create (int v)
{
Node *new;
new = calloc (1, sizeof *new);
new->value = v;
new->next = NULL;
return new;
}
/* add does NOT create - only handles wiring */
void add (Node **head, int v)
{
Node *new = create (v);
if (!*head) {
*head = new;
return;
}
Node *p = *head;
while (p && p->next)
p = p->next;
if (!(*head)->next)
(*head)->next = new;
else
p->next = new;
}
void search(Node *head)
{
Node *p = head;
while (p != NULL) {
printf (" address is %p; next address is %p;\n", p, p->next);
p = p->next;
}
}
void printvalues (Node *head)
{
Node *p = head;
unsigned cnt = 0;
while (p != NULL) {
printf (" node[%2u] value: %d\n", cnt++, p->value);
p = p->next;
}
}
Output
$ ./bin/dbgllmess
searching:
address is 0x1acf010; next address is 0x1acf030;
address is 0x1acf030; next address is 0x1acf050;
address is 0x1acf050; next address is 0x1acf070;
address is 0x1acf070; next address is (nil);
printing:
node[ 0] value: 0
node[ 1] value: 1
node[ 2] value: 2
node[ 3] value: 3
Note: you are responsible for freeing the memory allocated when it is no longer needed. Let me know if you have any questions.
Regarding the real question "why is every node's size in the heap is 16 bytes?"
Well, you can't expect that one memory block will lay exactly at the end of where the previous memory block sits. you can't assume anything about how the heap is managed intenally. two blocks can sit in a gigabyte distance from one another even if they allcoated consequently with malloc.
On Windows , In order for the heap to keep track of the memory blocks allocated, each block gets few more bytes to hold meta-data of the memory block. this is called "Heap Entry", and it is probably why your blocks are a bit bigger.
but again, you can't assume anything of the blocks - positioning in the heap anyway.

making custom Malloc, what is wrong here?

I've been working on a little custom worstfit Malloc using a double-linked list for a while, and although this is small I thought this would work. Is there anything obvious that is wrong with this code?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mymal.h"
typedef struct Node
{
int size;
int status;
struct Node *next;
struct Node *previous;
} Node;
Node *endNode;
Node *rootNode;
void *worstfit_mall(int size)
{
Node *theNode = sbrk (size + sizeof(theNode));
void *ptr;
if (rootNode == NULL)
{
theNode->status = 1;
theNode->size = size;
theNode->previous = theNode;
theNode->next = theNode;
rootNode = theNode;
endNode = theNode;
return theNode;
}
Node *worstNode;
worstNode = worstFit(size);
if (worstNode != NULL)
{
theNode->status = 1;
theNode->size = size;
Node *newNode = sbrk((worstNode->size - theNode->size) + sizeof(theNode));
newNode->status = 0;
newNode->size = worstNode->size - theNode->size;
theNode->next = newNode;
theNode->previous = worstNode->previous;
newNode->next = worstNode->next;
return newNode;
}
endNode->next = theNode;
endNode = theNode;
endNode->status = 1;
endNode->size = size;
ptr = sbrk(size + sizeof(theNode));
return ptr;
}
void my_free(void *ptr)
{
Node *pointer;
pointer = (Node*)ptr;
pointer->status = 0;
if ((pointer->next->status == 0) && (pointer->previous->status == 0))
sbrk(-1 * (pointer->next->size + pointer->size));
else if ((pointer->next->status == 1) && (pointer->previous->status == 0))
sbrk(-1 * (pointer->previous->size + pointer->size));
else if ((pointer->next->status == 0) && ( pointer->next->status == 0))
sbrk(-1 * (pointer->previous->size + pointer->next->size + pointer->size));
else
sbrk(-1 * pointer->size);
}
void *worstFit(int size)
{
Node *theNode = rootNode;
Node *worstNode;
while (theNode != NULL)
{
if ((worstNode == NULL || theNode->size > worstNode->size) && (theNode->size >= size) && (theNode->status == 0))
worstNode = theNode;
theNode = theNode->next;
}
return worstNode;
}
Here are the things that immediately strike me:
worstFit does not initialize worstNode to NULL and tries to read it while it's still garbage.
You create a linked list of Nodes, but the tail Node's next always points to itself. Meanwhile worstFit expects a NULL sentinel value when it iterates over the list.
worstfit_mall does not initialize endNode when initially creating rootNode.
worstfit_mall returns a pointer to the allocated Node, but if it's meant to be substitutable for malloc, it should be returning a pointer to memory that the caller is allowed to write to. You don't want the caller to scribble all over your Node data.
I'd expect worstfit_mall to return ((char*) node) + sizeof *node) (or more simply, node + 1) instead of returning node directly. my_free would need to do a corresponding, inverse adjustment to retrieve the Node pointer.
void my_free(void *ptr)
{
Node *nodePtr = ptr;
nodePtr--;
...
}
Additionally, it's unclear to me why worstfit_mall allocates memory via sbrk when going down the worstNode != NULL path. Isn't the point of this path to find an existing memory chunk to reuse? Furthermore, this path calls sbrk twice.
Finally, it appears to me that my_free unconditionally reduces the amount of allocated memory, but that would work only if you're freeing the last thing you allocated with sbrk. What if you called worstfit_mall twice and then called my_free on the first result? There is no path where my_free marks the memory chunk as no-longer-in-use so that worstfit_mall can reuse it later.
I don't know if there are other problems with your code; I would say that there very likely are given these types of fundamental issues.

Why a destroyed linked-list still could be read

I'm trying to destroy a single-linked-list, at first, my code of the destroy function like this:
void destroy_list_v0 (SLINK *list)
{
SLINK ptr = *list;
while (NULL != *list)
{
ptr = *list;
*list = (*list)->next;
free (ptr);
}
}
Function v0 performs perfect. here is output.
Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 5.
The 2nd element is 93.
The 3rd element is 92.
The 4th element is 70.
The 5th element is 92.
traverse_end & destroy_start.
destroy_end & traverse_start.
traverse_end.
All operations done.
Then I thought that single ponter is enough, so I adjust the function into single pointer version:
void destroy_list_v1 (SLINK list)
{
SLINK ptr = list;
while (NULL != list)
{
ptr = list;
list = list->next;
free (ptr);
}
}
Here is v1's output:
Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 27.
The 2nd element is 38.
The 3rd element is 20.
The 4th element is 66.
The 5th element is 30.
traverse_end & destroy_start.
destroy_end & traverse_start.
The 0th element is 0.
The 1st element is 32759808.
The 2nd element is 32759968.
The 3rd element is 32759936.
The 4th element is 32759904.
The 5th element is 32759872.
traverse_end.
All operations done.
To confirm that the destroy function is working fine, I traverse the linked list after it is destroyed. I found that the list could be read(in case of v0, it could not be read), though the value of every node has changed and indeterminacy. I thought after v0 performs, the pointer of the list point to NULL, but after v1 performs, it still point to original address. To test this idea, I adjust the v0 to v2:
void destroy_list_v2 (SLINK *list)
{
SLINK p_list = *list;
SLINK ptr = *list;
while (NULL != *p_list)
{
ptr = *p_list;
p_list = p_list->next;
free (ptr);
}
}
here is the v2 output:
Input a number for length of the link.
init_start.
init_end & traverset_start.
The 0th element is 0.
The 1st element is 76.
The 2nd element is 53.
The 3rd element is 80.
The 4th element is 31.
The 5th element is 97.
traverse_end & destroy_start.
destroy_end & traverse_start.
The 0th element is 0.
The 1st element is 13860864.
The 2nd element is 13861024.
The 3rd element is 13860992.
The 4th element is 13860960.
The 5th element is 13860928.
traverse_end.
All operations done.
I think my analysis is right, but it lead to new question.
The node struct is here:
typedef struct tag_node
{
int elem;
struct tag_node *next;
}NODE, *SLINK; //SLINK means SINGLE LINK
I have 2 questions:
1: The pointer 'next' is stored in the memory space which current pointer point to, after free current node, why the memory space of the pointer 'next' still could be read? Is the pointer 'next' still alive? I have this question because I thought that after v1 or v2 performs, it should be only the header node that could be read.
2: I thoutht v1 and v2 destroy the whole list, after v1 or v2 performs, why the value of header is still? I thought it should be like 1st to 5th that had changed to an indeterminate number.
Here is the whole code and the environment is Debian, clang:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define i_track(n) printf ("The %s's is %d.\n", #n, (n))
#define s_track(n) printf ("%s.\n", #n);
typedef struct tag_node
{
int elem;
struct tag_node *next;
}NODE, *SLINK; //SLINK means SINGLE LINK
void node_track (SLINK list);
NODE *node_generate (void);
SLINK init_list (int len);
SLINK locate_cur (SLINK list, int target_elem);
void insert_node (SLINK *list, int new_elem, int tag_elem);
SLINK traverse_list (SLINK list);
void clear_list (SLINK list);
void destroy_list_v0 (SLINK *list);
void destroy_list_v1 (SLINK list);
void destroy_list_v2 (SLINK *list);
void list_info (SLINK list, int node_elem);
int main (int argc, char *argv[])
{
int len;
SLINK list;
printf ("Input a number for length of the link.\n");
scanf ("%d", &len);
s_track(init_start);
list = init_list (len);
s_track(init_end & traverset_start);
traverse_list (list);
s_track(traverse_end & destroy_start);
// destroy_list_v0 (&list);
// destroy_list_v1 (list);
destroy_list_v2 (&list);
s_track(destroy_end & traverse_start);
traverse_list (list);
s_track(traverse_end);
s_track(All operations done);
return EXIT_SUCCESS;
} /* ---------- end of function main ---------- */
NODE *node_generate (void)
{
NODE *new_node = malloc (sizeof (NODE));
if (NULL == new_node)
{
printf ("ERROR: malloc failed.\n");
exit (EXIT_FAILURE);
}
memset (new_node, 0, sizeof(NODE));
return new_node;
}
SLINK locate_cur (SLINK list, int target_elem)
{
NODE *prev, *cur;
prev = node_generate ();
cur = node_generate ();
for (prev = list, cur = list->next; NULL != cur && target_elem != cur->elem; prev = cur, cur = cur->next)
;
return cur;
}
void insert_node (SLINK *list, int new_elem, int tag_elem)
{
NODE *new_node = node_generate ();
NODE *cur = *list;
new_node->elem = new_elem;
if ((int)NULL == tag_elem)
{
new_node->next = (*list)->next;
(*list)->next = new_node;
}
else
{
*list = locate_cur (cur, tag_elem);
new_node->next = (*list)->next;
(*list)->next = new_node;
}
}
SLINK init_list (int len)
{
SLINK header = node_generate ();
srand ((unsigned) time(0));
int elem;
for (int i = 0; i < len; i++)
{
elem = rand () % 100;
if (4 == elem / 10)
{
elem = elem + 50;
}
if (4 == elem % 10)
{
elem = elem + 5;
}
if (0 == elem % 100)
{
elem = elem + 999;
}
insert_node (&header, elem, (int)NULL);
}
return header;
}
void clear_list (SLINK list)
{
for (SLINK cur = list->next; NULL != cur; )
{
cur = cur->next;
free (list->next);
list->next = cur;
}
}
void destroy_list_v0 (SLINK *list)
{
SLINK ptr = *list;
while (NULL != *list)
{
ptr = *list;
*list = (*list)->next;
free (ptr);
}
}
void destroy_list_v1 (SLINK list)
{
SLINK ptr = list;
while (NULL != list)
{
ptr = list;
list = list->next;
free (ptr);
}
}
void destroy_list_v2 (SLINK *list)
{
SLINK p_list = *list;
SLINK ptr = *list;
while (NULL != p_list)
{
ptr = p_list;
p_list = p_list->next;
free (ptr);
}
}
SLINK traverse_list (SLINK list)
{
SLINK ptr = list;
for (int node_num = 0; ptr != NULL; ptr = ptr->next)
{
list_info (ptr, node_num);
++node_num;
}
return list;
}
void list_info (SLINK list, int node_num)
{
if (1 == node_num % 10 && 11 != node_num)
{
printf ("The %dst element is %d.\n", node_num, list->elem);
}
else if (2 == node_num % 10 && 12 != node_num)
{
printf ("The %dnd element is %d.\n", node_num, list->elem);
}
else if (3 == node_num % 10 && 13 != node_num)
{
printf ("The %drd element is %d.\n", node_num, list->elem);
}
else
{
printf ("The %dth element is %d.\n", node_num, list->elem);
}
}
void node_track (NODE *flag)
{
printf ("The flag element is %d.\n", flag->elem);
}
Freeing the memory is not the same thing as changing the address contained in the pointer variable.
The call to free releases the memory back to the heap managed by malloc. If you have a variable still pointing to the memory you had previously allocated, it is still pointing there after the free operation. However, it is a bug to use the pointer for anything after the free.
If you want to ensure your linked list does not still point to freed memory, you can assign NULL to each pointer in the structure after the associated memory has been freed.
Get used to this in C. The phrase "the behavior is undefined" is a mantra you will soon get used to, and it means doing certain things can lead to anything from a crash to apparently perfect behavior.
Pointers are a classic case of this mantra. You freed the memory and can still access it? Well, it's undefined. Wait, it's daylight savings time and now it crashed? Well it's undefined. Wait, you ran it on Windows and it works fine except on days ending in Y? Well it's undefined.
Remember the mantra; it will serve you well. Expecting C to complain loudly when you do something wrong is the wrong expectation, and keeping this in mind can save you much grief and tears.
Welcome to the land of C, where you can do anything (even if it's not legal). What you've done is invoked undefined behavior. You're not allowed to access the deleted memory anymore, but no one is stopping you from trying. C doesn't check that you are indeed accessing valid memory. It just goes ahead and does what you tell it to, even if you tell it to do something wrong. The fact that it "worked" is a mixture of luck and implementation defined stuff. Bugs like these are hard to find because instead of crashing, the program continues, and you don't find the bug until a long while later when it finally does start crashing. Once you invoke undefined behavior (which is what happens when you access deleted memory), anything can happen, from a black hole opening up and swallowing us all, to the program crashing, to the program appearing to work just fine.
v1 and v2 do destroy the whole list, but freeing memory doesn't mean you also erase the values in memory. The values are still there, you just no longer are allowed to access them because you've given those memory buckets back to the OS. The buckets still hold values. They're just not yours anymore.
free() marks the buffer as free, which means that subsequent malloc() could use the same date area, it doesn't mean that it will be erased or anything, but it could be returned to the operating system (and hence accessing it could cause a segmentation fault).
Accessing the freed memory is a bug because even if it usually is still there untouched, you could be accessing memory used by some other functions.
C99 says:
The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

Resources