Seg Fault in C Hash Table implementation - c

I've been working on this dynamic memory allocator problem set where we must implement malloc and free and I was struggling a lot with the implementation of free. Outside of freeing the appropriate memory, there is a statistics struct that we must update with each call of malloc/free which holds a variable for the size of active allocations, active_size. To accurately update active_size the problem set says we should implement a hash table. I made the hash table to hold malloc pointers and the size of their allocations and I included a lookup_size function which would then be used in the free function to update the active_size. However, it seems the lookup_size functions is flawed and causes a seg fault at this line:
return tmp->payload;
I'll leave below the entire code as well in case anyone catches any other mistakes, but it would be a huge help if someone could figure out the cause of this seg fault. Thanks.
#define M61_DISABLE 1
#include "m61.hh"
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cinttypes>
#include <cassert>
// Hash table size
#define TABLE_SIZE 10
/// m61_malloc(sz, file, line)
/// Return a pointer to `sz` bytes of newly-allocated dynamic memory.
/// The memory is not initialized. If `sz == 0`, then m61_malloc must
/// return a unique, newly-allocated pointer value. The allocation
/// request was at location `file`:`line`.
static m61_statistics stat_count = {0, 0, 0, 0, 0, 0, 0, 4294967295};
// Hash table item defintion
typedef struct ht_item {
void* address;
unsigned long long payload;
struct ht_item* next;
} ht_item;
// Initialize hash table
ht_item* hash_table[TABLE_SIZE];
// Mod hash function
int hash_func(void* address) {
uintptr_t hash_value = (uintptr_t) address % TABLE_SIZE;
return hash_value;
}
// Empty hash table
void init_hash_table() {
for (int i = 0; i < TABLE_SIZE; i++) {
hash_table[i] = NULL;
}
}
// Hash table item insertion function
bool insert_item(ht_item* item) {
// Check if there actually is an item
if (item == NULL) {
return false;
}
// Insert item to front of the l_list (assign current val to next and new value to hash table)
int index = hash_func(item->address);
item->next = hash_table[index];
hash_table[index] = item;
return true;
}
// Hash table functiont that finds allocated sizes by mallocc
unsigned long long lookup_size(void* p) {
if (p == NULL) {
return 0;
}
int index = hash_func(p);
ht_item* tmp = hash_table[index];
while (tmp != NULL && tmp->address != p) {
tmp = tmp->next;
}
return tmp->payload;
}
void* m61_malloc(size_t sz, const char* file, long line) {
(void) file, (void) line; // avoid uninitialized variable warnings
// Your code here.
if (!base_malloc(sz)){
++stat_count.nfail;
stat_count.fail_size += sz;
}
else {
++stat_count.nactive;
++stat_count.ntotal;
stat_count.active_size += sz;
stat_count.total_size += sz;
init_hash_table();
void* p = base_malloc(sz);
ht_item* malloc_data = (ht_item*) malloc(sizeof(ht_item));
malloc_data->address = p;
malloc_data->payload = sz;
malloc_data->next = NULL;
insert_item(malloc_data);
}
return base_malloc(sz);
}
/// m61_free(ptr, file, line)
/// Free the memory space pointed to by `ptr`, which must have been
/// returned by a previous call to m61_malloc. If `ptr == NULL`,
/// does nothing. The free was called at location `file`:`line`.
void m61_free(void* ptr, const char* file, long line) {
(void) file, (void) line; // avoid uninitialized variable warnings
// Your code here.
if (ptr){
--stat_count.nactive;
stat_count.active_size -= lookup_size(ptr);
}
base_free(ptr);
}

Before getting into the answer, let me say this:
Dropping a chunk of memory management code on someone with no way to run it besides reverse-engineering the code from compiler output until it works is not nice.
Please, take a good look at this link before asking another question. https://stackoverflow.com/help/minimal-reproducible-example
So, you have this snippet of code:
while (tmp != NULL && tmp->address != p) {
tmp = tmp->next;
}
return tmp->payload;
Look at the loop condition:
while (tmp != NULL && tmp->address != p) { ... }
Assuming tmp->address never equals p, it keeps iterating as long as tmp is not NULL. In other words, it stops when tmp is NULL.
Then, in the very next line, you try to access tmp. But tmp is already NULL at that point! So you pretty much do this:
return NULL->payload;
And that's what causes the segmentation fault.
If this loop is meant to always succeed when free is called on a valid pointer, then you're probably not reliably calling insert_item in some case (in m61_malloc)?
Or, if insert_item should not be called on every call to m61_malloc, then you would not expect to find the item on every call to lookup_size, so you should check that tmp is not NULL after the loop. (you should probably do this regardless, because someone might call m61_free with an invalid pointer).
As for the cause of this bug, m61_malloc seems pretty fishy...
if (!base_malloc(sz)) {
...
} else {
...
void* p = base_malloc(sz);
...
}
return base_malloc(sz);
What do you expect this to do when the first call to base_malloc does not return NULL? As it stands, it goes into the 'else' branch, calls base_malloc a second time (either allocating again or returning a NULL that isn't handled), then calls base_malloc a third time in the return statement (again, possibly returning NULL).
In this scenario, the pointer that is stored in the hash table is the one that was returned by the second call to base_malloc, but the pointer that is returned (and that might be passed into m61_free later) is the one returned by the third call to base_malloc (which is NOT stored in the hash table).
Maybe this is the true cause of the error?
EDIT: changing m61_malloc as below fixes this particular segfault, though it's still wrong.
if (!base_malloc(sz)) {
...
return NULL;
} else {
...
void* p = base_malloc(sz);
...
return p;
}

Related

My implementation of a linear probing hashmap in C is leaking memory in the resize function

Problem
I am implementing a simple linear probing hashmap in C and when running some tests I am noticing that some memory is being leaked only when the resize_hash_map function is called during the hash_map_add process.
I have referenced this book for the implementation of this hashmap: https://opendatastructures.org/
Environment
I am compiling using the gcc compiler with the following flags -std=c17 -Wall -g for the MacOS aarch64 architecture (Apple Silicon).
HashMap Implementation
The code for the HashMap implementation is as follows:
hash_map.h
#ifndef TERRACRAFT_HASH_MAP_H
#define TERRACRAFT_HASH_MAP_H
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#ifndef NDEL
#define NDEL ((void*) 1) // void pointer del tag
#endif
typedef struct HashMap HashMap;
struct HashMap {
void** items; // pointer to array of pointers to items
char** keys; // pointer to array of strings
unsigned long size;
unsigned long non_null;
unsigned long capacity;
};
HashMap* create_hash_map(unsigned long capacity);
void destroy_hash_map(HashMap* map);
void resize_hash_map(HashMap* map);
void* hash_map_get(HashMap* map, char* key);
bool hash_map_add(HashMap* map, char* key, void* item);
// TODO remove function
#endif //TERRACRAFT_HASH_MAP_H
hash_map.c
#include "collections/hash_map.h"
// based on public domain implementation from: http://www.cse.yorku.ca/~oz/hash.html
// note: static here limits this function to this file only
static unsigned long sdbm_hashcode(const char* str) {
unsigned long hash = 0;
int c;
while ((c = *str++)) hash = c + (hash << 6) + (hash << 16) - hash;
return hash;
}
HashMap* create_hash_map(unsigned long capacity) {
size_t val_bytes = sizeof(void*) * capacity;
size_t key_bytes = sizeof(char*) * capacity;
// create and zero arrays
void** items = malloc(val_bytes);
memset(items, NULL, val_bytes);
char** keys = malloc(key_bytes);
memset(keys, NULL, key_bytes);
HashMap* map = malloc(sizeof(HashMap));
map->items = items;
map->keys = keys;
map->size = 0;
map->non_null = 0;
map->capacity = capacity;
return map;
}
void destroy_hash_map(HashMap* map) {
for (int i = 0; i < map->size; i++) {
if (map->items[i] != NULL) {
free(map->items[i]);
}
if (map->keys[i] != NULL) {
free(map->keys[i]);
}
}
free(map->items);
free(map->keys);
free(map);
map = NULL;
}
void resize_hash_map(HashMap* map) {
printf("resizing hash map\n");
// TODO: this may be leaking memory
// smallest non-negative integer d, where 2d ≥ 3n
// we maintain this invariant to support hash functions that only work on table sizes that are a power of 2
unsigned long d = 1;
// bit shift d to go through the base 2 powers: 2^d until we find a value where 2^d >= 3n
// the bit shift is an efficient way to do base 2 powers.
while ((1<<d) < 3*map->capacity) d++;
unsigned long new_len = (1<<d);
// create and zero new arrays
size_t item_bytes = sizeof(void*) * new_len;
size_t key_bytes = sizeof(char*) * new_len;
void** new_item_arr = malloc(item_bytes);
memset(new_item_arr, NULL, item_bytes);
char** new_key_arr = malloc(key_bytes);
memset(new_key_arr, NULL, key_bytes);
map->size = map->capacity;
map->non_null = map->size;
// copy old arrays to new array
for (int i = 0; i < map->size; i++) {
if (map->keys[i] == NULL || map->keys[i] == NDEL) continue;
unsigned long k = sdbm_hashcode(map->keys[i]) % new_len;
// linearly prob from position k
while (new_key_arr[k] != NULL) k = (k == new_len-1) ? 0 : k + 1; // increment with wrap around
new_key_arr[k] = map->keys[i];
new_item_arr[k] = map->items[i];
}
// free pointers to old arrays but not their contents since the contents were moved to the new array
free(map->keys);
free(map->items);
// set new arrays
map->keys = new_key_arr;
map->items = new_item_arr;
// set new capacity
map->capacity = new_len;
}
void* hash_map_get(HashMap* map, char* key) {
unsigned long hash = sdbm_hashcode(key) % map->capacity;
// linear probe to check for key
while(map->keys[hash] != NULL) {
// check if the key was found
if (map->keys[hash] != NDEL && strcmp(map->keys[hash], key) == 0) {
return map->items[hash];
}
hash = (hash == map->capacity-1) ? 0 : hash + 1;
}
// key not in table, return nothing
return NULL;
}
bool hash_map_add(HashMap* map, char* key, void* item) {
void* existing_item = hash_map_get(map, key);
if (existing_item != NULL && existing_item != NDEL) return false;
if (2*(map->non_null+1) > map->capacity) resize_hash_map(map); // allow for a maximum of 50% occupancy
unsigned long hash = sdbm_hashcode(key) % map->capacity;
// linear probe to insert
while (map->keys[hash] != NULL && map->keys[hash] != NDEL) hash = (hash == map->capacity-1) ? 0 : hash + 1;
if (map->keys[hash] == NULL) map->non_null++;
map->size++;
// create memory for string and copy key to that block of memory
size_t key_len = strlen(key);
map->keys[hash] = malloc(key_len * sizeof(char));
strncpy(map->keys[hash], key, key_len+1);
map->keys[hash][key_len] = '\0'; // strncpy does not copy terminator, manually add it
// set the item
map->items[hash] = item;
return true;
}
Checking for leaks
Using the following driving program, I checked for leaks using the MacOS leaks command:
main.c
#include <stdio.h>
#include <stdlib.h>
#include "collections/hash_map.h"
typedef struct {
int x, y;
} Point;
void print_point(Point* point) {
printf("Point: (%d, %d)\n", point->x, point->y);
}
int main() {
HashMap* map = create_hash_map(2);
Point* one = malloc(sizeof(Point));
one->x = 1;
one->y = 2;
Point* two = malloc(sizeof(Point));
two->x = 3;
two->y = 4;
hash_map_add(map, "one", one);
/*
* Note:
* When adding an item that will cause the hash map to resize, a memory leak is introduced.
*/
hash_map_add(map, "two", two);
Point* one_result = hash_map_get(map, "one");
print_point(one_result);
Point* two_result = hash_map_get(map, "two");
print_point(two_result);
destroy_hash_map(map);
// wait for user input, gives me time to run the MacOS 'leaks' command on the executable
// Is there a better way besides running valgrind on a linux vm? I'm on an Apple silicon mac.
getchar();
return 0;
}
I run the executable and then while that is waiting for user input, I run the 'leaks' command as follows: leaks terracraft (note: terracraft is the name of the executable).
The expected report is that there are 0 leaks, however, the program (leaks) indicates that my running executable has 2 leaks.
leaks output
The output of running the leaks command is:
Process 64507 is not debuggable. Due to security restrictions, leaks can only show or save contents of readonly memory of restricted processes.
Process: terracraft [64507]
Path: /Users/USER/*/terracraft
Load Address: 0x1027bc000
Identifier: terracraft
Version: ???
Code Type: ARM64
Platform: macOS
Parent Process: clion [62506]
Date/Time: 2023-02-10 09:09:51.751 -0400
Launch Time: 2023-02-10 09:09:44.092 -0400
OS Version: macOS 12.6.3 (21G419)
Report Version: 7
Analysis Tool: /usr/bin/leaks
Physical footprint: 1617K
Physical footprint (peak): 1617K
----
leaks Report Version: 4.0
Process 64507: 1059 nodes malloced for 92 KB
Process 64507: 2 leaks for 32 total leaked bytes.
2 (32 bytes) << TOTAL >>
1 (16 bytes) ROOT LEAK: 0x600001120180 [16]
1 (16 bytes) ROOT LEAK: 0x6000011201c0 [16]
Thank you for your time :)
Minor issues
Insufficient allocation
This ...
// create memory for string and copy key to that block of memory
size_t key_len = strlen(key);
map->keys[hash] = malloc(key_len * sizeof(char));
... allocates insufficient space for a copy of the given key. You also need to provide for the string terminator, too.
strncpy is not your friend
The strncpy() function is suited only to a certain few rather special purposes. Generally, you should avoid it. Often, as here, you don't need it anyway. Where you do want a bounded copy, strncat() can do that -- just prime the destination by writing a string terminator in the first position.
Continuing on from the previous code, this ...
strncpy(map->keys[hash], key, key_len+1);
... overruns the bounds of the (too-small) allocated space. Note well that strncpy() does not save you from that, because you tell it that the space is larger than it actually is.
For the particular case of making a dynamically-allocated copy of a string, you can probably save yourself effort and trouble by using strdup() to achieve that in one call, with no need to manually compute any sizes. Note well that even though there is not then an explicit malloc(), you are responsible for free()ing the result when you no longer need it. Note also, that strdup() is specified by POSIX, but not by the C language specification. I'm pretty sure your implementation provides it, but if you need broad portability then relying on strdup() complicates that.
Non-idiomatic code
This ...
hash = (hash == map->capacity-1) ? 0 : hash + 1;
... seems correct, but it's harder to read and understand than this equivalent:
hash = (hash + 1) % map->capacity;
Null pointer representation
This ...
// create and zero arrays
void** items = malloc(val_bytes);
memset(items, NULL, val_bytes);
... has at least two problems:
The second argument to memset should be an int, and although NULL could be an int in your implementation, it is more likely a void *. The language spec provides for explicit pointer-to-integer conversions, but not for implicit ones. When you mean the integer 0, write 0.
C does not require that the representation of a null pointer be all-bits-zero. If that does happen to be the (a) null-pointer representation of your implementation, which is common, then memset(pointer_to_pointers, 0, num_bytes) does set pointer objects in the space to which pointer_to_pointers points to null. If that is not the (appropriate) null pointer representation, however, then that call will not set the pointers to null.
But if your intent is to obtain memory set to all-bits-zero, then how about using calloc() instead of malloc()? For example,
void** items = calloc(new_len, sizeof(*items));
That even relieves you of the need to perform a separate computation of the number of bytes required.
Main Issue
Running your program under Valgrind reveals leakage of four objects, two allocated directly in function main (at lines 16 and 20), and two allocated in hash_map_add (both at line 133).
The ones allocated in main() are your two Point objects. This is strange because although you do not free those in main() your destroy_hash_map() function attempts to free them (though the wisdom of this is suspect).
The ones allocated in hash_map_add() are the copies of the keys. These are initially stored in the map, so either they are lost during some manipulation of the map, or they are not freed when the map is destroyed. Observations:
the key copies are still lost if I increase the initial capacity of the map so that resize_hash_map() is never called, so the problem seems not to be there.
If I comment out the call to destroy_hash_map() then instead of reporting the keys as "definitely lost", is says that they are "indirectly lost", which tells me that the map continues to hold pointers to those keys until it is destroyed, but fails to free them during its destruction.
That got me looking at destroy_hash_map(), and after a bit of study I realized that the problem is here:
for (int i = 0; i < map->size; i++) {
The map always has more buckets than map->size, and the buckets in use are not necessarily the first ones. You need to loop over all map->capacity buckets to free the keys (and the items, if that's really what you want to do).
There are errors in both resize_hash_map() and destroy_hash_map(), but the memory leak is in destroy_hash_map().
The main error in resize_hash_map() is here:
map->size = map->capacity;
map->non_null = map->size;
// copy old arrays to new array
for (int i = 0; i < map->size; i++) {
It should not be changing map->size and map->non_null. Remove those lines and change the loop termination condition to i < map->capacity. The above code becomes:
// copy old arrays to new array
for (int i = 0; i < map->capacity; i++) {
The main error in destroy_hash_map() is that it is using an incorrect loop termination condition:
for (int i = 0; i < map->size; i++) {
Rather than checking i < map->size, it should be checking i < map->capacity, like this:
for (int i = 0; i < map->capacity; i++) {
There is also a buffer overflow in hash_map_add() because the nemory allocated to hold the key string is not large enough to hold the null terminator:
// create memory for string and copy key to that block of memory
size_t key_len = strlen(key);
map->keys[hash] = malloc(key_len * sizeof(char));
strncpy(map->keys[hash], key, key_len+1);
map->keys[hash][key_len] = '\0'; // strncpy does not copy terminator, manually add it
The above code should be changed to something like this:
// create memory for string and copy key to that block of memory
size_t key_len = strlen(key);
map->keys[hash] = malloc((key_len + 1) * sizeof(char));
strcpy(map->keys[hash], key);
(Note: the * sizeof(char) part can be omitted because sizeof(char) is 1 by definition.)
Another problem is the use of NULL as the second argument in the calls to memset(). NULL might be defined to expand to a integer constant of value 0, but it is more likely to be defined as such a constant converted to void *. That would lead to compiler warnings about conversions from pointers to integers. NULL is intended to be used for setting pointers to null pointer values or for comparing pointer values to null pointer values. Change the second argument of the memset() calls from NULL to 0 for portability.
You're not freeing all memory.
Every pointer returned by malloc must eventually be freed with free.
Add this:
void* MALLOC(size_t size)
{
void* p = malloc(size);
printf("MALLOC: %p\n", (void*)p);
return p;
}
void FREE(void *p)
{
free(p);
printf("FREE: %p\n", (void*)p);
}
and replace malloc with MALLOC and free with FREE.
The output will be something like this:
MALLOC: 0000014FD02CA5D0
MALLOC: 0000014FD02CA890
MALLOC: 0000014FD02CA070
MALLOC: 0000014FD02C6F30 // not freed
MALLOC: 0000014FD02C6F50 // not freed
MALLOC: 0000014FD02C40B0 // not freed
resizing hash map
MALLOC: 0000014FD02C5AF0
MALLOC: 0000014FD02CFF30
FREE: 0000014FD02CA890
FREE: 0000014FD02CA5D0
MALLOC: 0000014FD02C40D0
Point: (1, 2)
Point: (3, 4)
FREE: 0000014FD02C5AF0
FREE: 0000014FD02CFF30
FREE: 0000014FD02CA070
(The // not freed comments are not part of the output, it's only for illustration).
You can see that there are definitely more MALLOCs than FREEs. More specicically the addresses with the // not freed comment are never freed.
The problem is most likely in hash_map_add.

Segmentation fault when strdupa void pointer

I'm fairly new to pointers, and void pointers is still black art to me.
In the following code I get a segfault when tmp->item = strdup(item);
I'm not sure how to fix.
int springmap_add(SpringMap *sm, char *key, void *item) {
SpringMap *tmp = sm;
.
.
.
while (tmp) {
if (!tmp->next) {
tmp->next = springmap_init();
tmp->next->key = strdup(key);
tmp->next->item = strdup(item); // Segfault here
return 1;
}
tmp = tmp->next;
}
return 0
}
int main(int argc, char* argv[]) {
char* key[ESTS] = {"alpha"};
void* ptr[ESTS] = {(void*)0xdeadbeef};
SpringMap* map = springmap_init();
for(int i = 0; i < TESTS; i++) {
int status = springmap_add(map, key[i], ptr[i]);
}
springmap_free(map);
return 0;
I'm not up to speed on void pointers.
The function name already tells: strdup composes of string duplicate, and it only is able to duplicate null-terminated C-strings (well, admittedly any data as long as it contains a null byte somewhere, though it would get cut off too early unless this null byte was the very last byte within the data).
void pointers in C have the unfortunate nature of implicitly converting to any other pointer type, happening in your code as well. However these pointers do not point to null-terminated C-strings, actually, they aren't even valid at all (most of most likely, at least)! Thus trying to read from them yields undefined behaviour.
So at first make sure that your void pointers point to valid memory. To use strdup they should point to C-strings, otherwise memcpy is the way to go, though you need to malloc storage space as target first. For both, you need the size of the object available, though. However you cannot get that back from the void pointer any more, thus you need yet another parameter.
You could write your own objdup function covering the duplication:
void* objdup(size_t size, void* object)
{
void* copy = malloc(size);
if(copy)
{
memcpy(copy, object, size);
}
return copy;
}
Still your pointers need to be valid! Some possible example might look like:
int main()
{
SomeDataType o1;
AnotherDataType o2;
AnotherDatatType* ptr = &o2; // create a valid pointer
// (could be done by malloc'ing memory, too)
void* c1 = objdup(sizeof(o1), &o1);
// ^ take the address of a REAL object!
if(c1)
{
void* c2 = objdup(sizeof(*o2), o2); // or via pointer to REAL object
if(c2)
{
// ...
free(c2);
}
free(c1);
}
return 0;
}

How to set arraylist as empty after free?

I'm testing my array_list with some test:
I need to check if my array list is empty after the de-allocation,
but I have some problems
...
typedef struct array_list{
void** array;
size_t size;
size_t capacity;
}array_list_t;
array_list_t* array_list_new(size_t capacity) {
array_list_t* result = (array_list_t*) malloc(sizeof(array_list_t));
result->array = (void**) malloc(sizeof(void*)*capacity);
result->size = 0;
result->capacity = capacity;
return result;
}
void array_list_free(array_list_t* array) {
free(array->array);
free(array);
}
int array_list_is_empty(array_list_t* list){
if(list->size == 0){
return 1;
}else{
return 0;
}
}
#include "unity.h"
#include "array_list.h"
...
int main () {
array_list_t* array = array_list_new(10);
TEST_ASSERT_EQUAL_INT(1, array_list_is_empty(array)); // 1 == 1 OK
array_list_insert(array,new_int(1));
TEST_ASSERT_EQUAL_INT(0, array_list_is_empty(array)); // 0 == 0 OK
array_list_free(array);
TEST_ASSERT_EQUAL_INT(1, array_list_is_empty(array)); // 1 == 0 NOT_EQUAL
}
I thought to solve this problem setting the size as 0 after free,
for example:
(... free(array); array->size = 0; array->capacity = 0; array = NULL; ...)
How do I solve this problem?
Once you do free(array) in array_list_free(), it's no longer valid to dereference array. So you need to set the array variable to NULL in main():
array_list_free(array);
array = NULL;
Then array_list_is_empty() can check whether its argument is NULL before testing the size:
int array_list_is_empty(array_list_t *list) {
return list == NULL || list->size == 0;
}
A better design would be for array_list_free() to just free array->array, and allow the caller to do free(array) when it's done with that array list. This is the usual approach: whichever component allocates an object is responsible for freeing it.
i'm testing my array_list with some test: I need to check if my array list is empty after the deallocation, but i have some problems
Yes, you have an insurmountable problem: once you free the array_list_t structure as array_list_free does, attempting to access it produces undefined behavior. The result of any test you attempt to perform on it at that point therefore does not yield any useful information (because: undefined), and the attempt might have any result whatever within the power of your C implementation, with crashing the program being an altogether plausible possibility.
I thought to solve this problem setting the size as 0 after free, for example : (... free(array); array->size = 0; array->capacity = 0; array = NULL; ...)
That's a fine alternative for clearing the list without freeing it. In particular, setting the capacity to 0 is a natural indication that the element array needs to be (re)allocated. But again, once you free the list structure itself, there's nothing more you can or should do with it. It's lifetime is over. It's kicked the bucket, it's shuffled off its mortal coil, run down the curtain and joined the bleedin' choir invisible!! THIS IS AN EX-LIST!! (Apologies to Monty Python)
you could do a=NULL; after calling array_list_free(a) to indicate that a is no-longer usable.
Or you could modify array_list_free(a) to not free a but instead set a->capacity and a->size to 0 and set a->array to NULL leaving *a in an empty looking state.

Freeing all allocated memory in case of failure

I'm working on a C project (assignment for school). One of the demands is that in case of malloc() failure, the program must free() all allocated memory and exit().
Consider a case where function A() constructs a linked-list and in each iteration it calls to another function, B(). Now, if a malloc failure occured at B(), it must free() the memory it allocated but function A() should do that as well.
Things are getting quite complicated when you have a tree of function calls larger than two.
In my previous project I used a flag to notify a malloc() failure - if a function uses another function which may use malloc(), it has to check the flag right after. It worked, but code got kinda messy.
Is there a neat solution for this problem?
Of course, with "real" applications all memory is de-allocated by the OS, but I guess this demand is pedagogical..
I think the easiest approach is to create a custom allocator (as somebody already noted in a deleted post) to keep track of all your allocations, then do a custom deallocator, use these for all your heap memory needs.
if a malloc fails you have the list of previously allocated blocks at easy reach.
e.g.
(you need to redo this cause it is not effective and should be optimized but shows the principle and only ocular compilation)
typedef struct
{
void* pMemory; /* for the allocated memory */
size_t size; /* for better debugging */
} MemoryBlock;
#define MAXBLOCKS 1000
MemoryBlock myheap[MAXBLOCKS]; // global so zero:ed
static int block = 0;
void* myalloc(size_t size)
{
static int block = 0;
// you should check vs MAXBLOCKS
myheap[block].pMemory = malloc(size);
myheap[block].size = size;
// check if it failed.
if ( myheap[block].pMemory == NULL )
{
for (int i = 0; i < block; ++i)
{
myfree(myheap[i].pMemory);
}
fprintf( stderr, "out of memory\n");
exit(EXIT_FAILURE);
}
else
{
return myheap[block++].pMemory;
}
}
void myfree(void* p)
{
for (int i = 0; i < block; ++i)
{
if ( p == myheap[i].pMemory )
{
free(myheap[i].pMemory);
myheap[i].pMemory = NULL;
return;
}
}
}
Yes. The best (and conventional) way is to initialize every pointer value to zero. Then set it during the malloc() assignment. Ex: myPtr = malloc( 10 );
It will be zero in case of failure, and you check that. And finally, when you go about freeing, you always check the pointer value before calling free():
if ( myPtr != 0 )
free( myPtr );
There is no need for an extra flag.
Are you having issue checking for errors or handling them? If you want info on catching them, use donjuedo's suggestion.
For ideas on freeing memory in the event of error, try one of these two methods:
1) For a uni-directional linked-list, keep a special pointer that points to the head of the list. In your cascading free function, start at the head, capture the next-pointer in a temp variable, free the head, move to the next structure in the list using the temp-pointer, and repeat the process until the next-pointer == 0.
2) For a bi-directional linked-list (my preference) you don't need to keep a special pointer to the head of the list. Assuming you are still at the tail, just capture the previous-pointer into a temp variable, free the tail, move back using the temp-pointer, and repeat the process until the previous-pointer == 0
You could look into the atexit() function, to register code that will be executed when the program terminates. Such code can then check if there is anything that needs to be free()d.
Note that atexit() has no way to unregister. So you need to make sure that you register each cleanup function only once, and that it does the right thing when there is nothing to clean up.
#include <stdlib.h>
#include <stdio.h>
int *ptr1;
char *ptr2;
int clean1_registered, clean2_registered;
void clean1(void)
{
printf("clean1 called\n");
if (ptr1) {
free(ptr1);
ptr1 = NULL;
}
}
void clean2(void)
{
printf("clean2 called\n");
if (ptr2) {
free(ptr2);
ptr2 = NULL;
}
}
void B(void)
{
ptr2 = malloc(100);
if (!clean2_registered) {
atexit(clean2);
}
}
void A(void)
{
ptr1 = malloc(100 * sizeof(int));
if (!clean1_registered) {
atexit(clean1);
}
B();
}
int main(int argc, char **argv)
{
A();
}

Memory Allocation Tracking in C -- Am I doing this right?

Just for fun (and for C programming practice) I wrote the following piece of code that does the following:
Acts as a tracking system for memory allocations
Frees all dynamically allocated memory with a function call
Here is the code:
typedef enum _OpMode {
OM_APPEND,
OM_DESTROY
} OP_MODE;
void refOp(void *ptr, OP_MODE mode) {
/* contains static array of pointers and provides an interface to that
array */
static void **references = NULL;
static int size = 0;
static int reset = 0;
if (reset) {
reset = 0;
references = NULL;
size = 0;
}
switch (mode) {
case OM_APPEND:
//add a pointer to reference array
references = (void**) realloc(references, sizeof(void*) * (size + 1));
references[size++] = ptr;
break;
case OM_DESTROY:
//free memory at all pointers kept in reference array
for (int i = 0; i < size; i++) {
free(references[i]);
references[i] = NULL;
}
free(references);
reset = 1;
break;
default:
printf("Invalid enum value '%d' passed as mode.\n", mode);
break;
}
}
void refDestroyAll() {
//Wrapper function
refOp(NULL, OM_DESTROY);
}
void *myAlloc(void* ptr, size_t size) {
/* Allocates memory and stores pointer copy in reference array */
void *tmp_ptr;
tmp_ptr = realloc(ptr, size);
refOp(tmp_ptr, OM_APPEND);
return tmp_ptr;
}
The idea is that one would use myAlloc() instead of malloc or realloc to dynamically allocate memory. And one would use refDestroyAll() to free all memory that was created with myAlloc().
I've done some testing, and it seems to be working, but I can't help feeling that I'm missing something important. Does this code actually work as intended, or am I leaking memory when I call refDestroyAll()?
You have a bug, that could cause a segmentation fault. realloc() could return the same pointer as it is given, in which case you would have added it twice to the array. When you call your free function, it would try and free the same pointer twice, resulting in a segmentation fault error.
Additionally, I don't understand why you have the reset parameter. Why not simply set references and size to 0 in the OM_DESTROY case? It is good practice to always set a pointer to NULL immediately after freeing it.

Resources