0

So my goal is to malloc a maze struct that contains a 2D array; however, when I try to allocate memory for each "cell" of the 2D array I can't seem to free it properly afterwards. Is there a way that I could malloc the struct in one line or at least in a way that I can easily free the allocated memory with the free_maze function? I've attached my .c file along with the header file that defines the struct. Additionally, I have attached an example of a maze that is contained within a text file.

#include <stdlib.h>
#include "maze.h"


Maze* malloc_maze(int num_rows, int num_cols){
  Maze* maze = malloc(sizeof(*maze));
  if (maze == NULL){
    free(maze);
    return NULL;
  }
  maze -> cells = malloc(sizeof(maze -> cells)*(num_cols));

  if (maze -> cells == NULL){
    free(maze);
    return NULL;
  }
  for(int i = 0; i < num_cols; i++){
    maze -> cells[i] = malloc(sizeof(*(maze -> cells))*(num_rows));
  }
  maze -> num_rows = num_rows;
  maze -> num_cols = num_cols;
  return maze;
}

void free_maze(Maze* maze){
  free(maze);
}

Maze* read_maze(FILE* fp){
  Maze* maze;
  char c = fgetc(fp);
  int rows = 0;
  int cols = 0;
  int chars = 0;
  while(c != EOF){
    chars++;
    c = fgetc(fp);
  }
  rewind(fp);
  while(c != '\n'){
    cols++;
    c = fgetc(fp);
  }
  rows = chars / cols;
  cols--;
  maze = malloc_maze(rows, cols);
  rewind(fp);
  for(int row_count =0; row_count <= rows; row_count++){
    for(int col_count = 0; col_count < cols; col_count++){
      fseek(fp, (row_count*(cols+1)+col_count), SEEK_SET);
      maze -> cells[col_count][row_count] = fgetc(fp);
    }
  }
  maze -> num_rows = rows;
  maze -> num_cols = cols;
  return maze;
}

bool write_maze(const char* filename, const Maze* maze){
  FILE* ha;
  ha = fopen(filename, "w");
  if(ha == NULL){
    return false;
  }
  rewind(ha);
  int rows = maze -> num_rows;
  int cols = maze -> num_cols;
  for(int i = 0; i < rows; i++){
    for(int j = 0; j < cols; j++){
    fputc(maze -> cells[j][i], ha);
    }
    fputc('\n', ha);
  }
  fclose(ha);
  return true;
}

/////////////////header file//////////////////////////

#ifndef MAZE_H
#define MAZE_H 

#define WALL 'X'
#define PATH ' '

#include <stdio.h>
#include <stdbool.h>

typedef struct _Maze {
   int num_rows; 
   int num_cols; 
   char** cells; 
} Maze;

Maze* malloc_maze(int num_rows, int num_cols);

void free_maze(Maze* maze){
        __attribute__((nonnull));
}

Maze* read_maze(FILE* fp){
        __attribute__((nonnull));
}

bool write_maze(const char* filename, const Maze* maze){
        __attribute__((nonnull));
}

///////////////example maze within .txt file/////////////////////


XXXXX XXX
X       X
X XXX XXX
X X X   X
X X XXXXX
X       X
XXXXX XXX
General Grievance
  • 4,555
  • 31
  • 31
  • 45
  • The dot `.` and arrow `->` operators bind very tightly and should never have spaces around them. – Jonathan Leffler Dec 11 '17 at 03:53
  • To answer your headline problem, for every `malloc()`, write a free in roughly the reverse order of the allocations. Free the rows of cells. Free the array of pointers that held the rows. Free the structure. Consider whether you can reduce the number of allocations but still write one free for every allocation. – Jonathan Leffler Dec 11 '17 at 03:57
  • @JonathanLeffler Out of curiosity, what does freeing in 'roughly the reverse order' improve? – Yashas Dec 11 '17 at 04:27
  • @Yashas: you can't access memory once you've freed it, so if the code freed the top-level pointer first, there'd be no way to free the the other pointers (or, more precisely, you'd have to save a copy of the dependent pointers before freeing the main pointer). So, free the most dependent pointers before freeing the intermediate ones. – Jonathan Leffler Dec 11 '17 at 04:31
  • Silly me. I did not realize that. – Yashas Dec 11 '17 at 04:32
  • chaotic. For example `free(NULL)` and few other problems – Jacek Cz Dec 11 '17 at 04:42
  • 1
    @JacekCz.: No problem with `free(NULL)` standard accepts it. *If ptr is a null pointer, no action occurs.* – user2736738 Dec 11 '17 at 04:49
  • 1
    @JacekCz: On further study, the `free(maze);` in the error recovery when `maze = malloc(…)` failed is pointless — harmless, but pointless. Freeing a null pointer is safe — it is defined to be a no-op. – Jonathan Leffler Dec 11 '17 at 05:05
  • [`fgetc` returns an `int`](https://stackoverflow.com/questions/35356322/difference-between-int-and-char-in-getchar-fgetc-and-putchar-fputc) – Antti Haapala -- Слава Україні Dec 11 '17 at 06:39
  • Every call to malloc must have a corresponding call to free, simple as that. – Lundin Dec 11 '17 at 10:17

2 Answers2

3

Given an allocator function, the deallocator writes itself - you free the pointers in roughly the reverse order they were allocated.

So, given that the allocator is (only reformatted from the question — functionality unchanged):

Maze *malloc_maze(int num_rows, int num_cols)
{
    Maze *maze = malloc(sizeof(*maze));
    if (maze == NULL)
    {
        free(maze);
        return NULL;
    }
    maze->cells = malloc(sizeof(maze->cells) * (num_cols));

    if (maze->cells == NULL)
    {
        free(maze);
        return NULL;
    }
    for (int i = 0; i < num_cols; i++)
    {
        maze->cells[i] = malloc(sizeof(*(maze->cells)) * (num_rows));
    }
    maze->num_rows = num_rows;
    maze->num_cols = num_cols;
    return maze;
}

the deallocator should be:

void free_maze(Maze *maze)
{
    for (int i = 0; i < num_cols; i++)
        free(maze->cells[i]);
    free(maze->cells);
    free(maze);
}

This makes sure the code doesn't try to access memory after it is freed.


However, closer analysis of the allocator shows that there are some (minor) problems. For example, normally you treat the pair of indexes as maze->cells[row][col], but the memory allocation requires it to be used as maze->cells[col][row]. Both can work, but the row-column order is more usual in C. Also, the sizes in the second and third malloc() calls are incorrect. Fortunately for you, the second one allocates in units of sizeof(char **) instead of sizeof(char *), but those are the same size, so it "doesn't matter". The third one allocates sizeof(char *) units, instead of sizeof(char), so there is much more memory allocated than memory (normally, sizeof(char *) is 4 or 8 bytes but sizeof(char) is 1 by definition).

So, you might do better to use this, which keeps the maze->cells[col][row] access notation:

Maze *malloc_maze(int num_rows, int num_cols)
{
    Maze *maze = malloc(sizeof(*maze));
    if (maze == NULL)
        return NULL;
    maze->cells = malloc(sizeof(maze->cells[0]) * num_cols);
    if (maze->cells == NULL)
    {
        free(maze);
        return NULL;
    }
    for (int i = 0; i < num_cols; i++)
    {
        maze->cells[i] = malloc(sizeof(maze->cells[0][0]) * num_rows);
        if (maze->cells[i] == 0)
        {
            for (int j = 0; j < i; j++)
                 free(maze->cells[j]);
            free(maze->cells);
            free(maze);
            return NULL;
        }
    }
    maze->num_rows = num_rows;
    maze->num_cols = num_cols;
    return maze;
}

This cleans up the partially allocated memory on allocation failure. It doesn't change the deallocation code (unless you want to add a null check, but if the allocation failed, you shouldn't be calling the deallocation code).

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    @coderredoc: Yes; see the amendment to the answer. It actually was "safe"; there was (more than) enough memory allocated. But the sizing was wrong, twice. – Jonathan Leffler Dec 11 '17 at 05:03
  • Thanks, your explanation was very helpful. I'm pretty new to using memory on the heap so the full explanation went a long way towards helping do the last parts of the code. – JustCProblems Dec 11 '17 at 19:27
2

NOTE: I believe the code automatically takes care of alignment in OP's case. I am still working on to tweak it to work with any data type of members of Maze. The alignment is broken if sizeof(char*) exceeds sizeof(T) where T is the type of cell[0][0].

You can allocate memory for the entire maze in one malloc call. This will allow you to free the memory in one free call. It improves performance because:

  • it requires a single malloc (and free) call
  • the memory allocated is contiguous (cache friendly)

Maze *malloc_maze(int num_rows, int num_cols)
{
    const size_t mem_size = sizeof(Maze)
                        + num_cols*sizeof(((Maze*)0)->cells[0]) /* indirection array */
                        + sizeof((((Maze*)0)->cells[0][0]))*num_cols*num_rows; /* matrix */

    void *block = malloc(mem_size);
    if(block == NULL)
        return NULL;

    Maze *maze = block;
    maze->cells = (void*)(maze + 1);
    block = &maze->cells[0] + num_cols;
    for(int i = 0; i < num_cols; i++)
    {
        maze->cells[i] = block; 
        block = &maze->cells[i][0] + num_rows;
    }
    
    maze->num_rows = num_rows;
    maze->num_cols = num_cols;
    return maze;
}
void free_maze(Maze *maze)
{
    free(maze);   
}
Community
  • 1
  • 1
Yashas
  • 1,154
  • 1
  • 12
  • 34
  • "You can allocate memory for the entire maze in one malloc call. " That is true if code took into account alignment requirements of `maze->cells` and `maze->cells[i]`, which it does not. This leads to _undefined behavior_ (UB). – chux - Reinstate Monica Dec 11 '17 at 15:39
  • I did not understand what alignment requirements are needed. – Yashas Dec 11 '17 at 15:40
  • What is the your understanding of alignment requirements? – chux - Reinstate Monica Dec 11 '17 at 15:42
  • I have absolutely no clue about what you are talking about. All I know is I need a indirection array of size of the major dimension where each of the element points to an array of data. – Yashas Dec 11 '17 at 15:43
  • @chux I have got a rough idea now from an [SO answer](https://stackoverflow.com/questions/12596183/how-to-dynamically-allocate-2d-array-thats-16b-aligned). Can you cite a good resource from where I can understand it in detail? I'll be quick to learn and update the answer asap. – Yashas Dec 11 '17 at 15:49
  • I do not know of a good resource - just search, you will find enough. Take your time. The key idea is that `maze->cells` and `maze->cells[i]` need to be on the right size multiple of an address. `void *block` is aligned right as `malloc()` guarantees that. `max_align_t` is a good keyword to search. – chux - Reinstate Monica Dec 11 '17 at 15:52
  • Here is what I understood: the address of every element of type `T` needs to be a multiple of `_Alignof(T)`. The normal way of allocating memory for 2D arrays using`malloc` works because `malloc` is guaranteed to addresses which are `_Alignof(max_align_t)` aligned. Since `max_align_t` is the largest possible alignment requirement of a scalar type and every other type is a collection of those scalar types, the alignment requirement for any type cannot be more than `max_align_t`. Mathematically, the alignment requirement of a type is the L.C.M of the `_Alignof` of all the types it consists of. – Yashas Dec 11 '17 at 16:24
  • Since they are always powers of 2, the previous statement can be simplified to: the alignment requirement of a non-scalar type is the alignment requirement of the largest type. Basically, in my answer, I need to ensure that everything of any type `T` which is given memory has an address which is a multiple of `_Alignof(T)`. I must also ensure that the indirection array and the subarrays are contiguous. – Yashas Dec 11 '17 at 16:25
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/160948/discussion-between-chux-and-yashas). – chux - Reinstate Monica Dec 11 '17 at 16:25
  • I think the middle section of the allocation will be sufficiently well aligned because the structure must be well enough aligned to accommodate the pointers in it, so the section of memory after the structure is well enough aligned to handle the pointers. One issue *might* be the trailing data section. If it stores a type with a more stringent alignment requirement than the pointers—a possibility if, for example, `long double` needs 16-byte alignment for 16-byte quantities, but pointers only need 8-byte alignment for 8-byte quantities and the array of pointers holds an odd number of pointers. – Jonathan Leffler Dec 11 '17 at 19:42
  • If you're worried about that, you could probably supplement the code with an appropriate static assertion using `_Alignof` etc. – Jonathan Leffler Dec 11 '17 at 19:45
  • `static_assert(_Alignof(((Maze*)0)->cells[0]) >= _Alignof(((Maze*)0)->cells[0][0]), "");`? – Yashas Dec 12 '17 at 04:54