0

I am writing a maze game and have come across some issues when running it through valgrind. The code below is a simplified version to demonstrate the basic structure of my game.

#include <stdio.h> #include <stdlib.h> void init(void); int play(void); typedef struct Node { int x, y; } Node; /* global variables */ Node *nodes; int width, height; int main(void) { width = height = 10; /* initial game maze dimensions */ do { init(); } while (play()); free(nodes); nodes = NULL; return 0; } void init(void) { nodes = calloc((size_t)(width * height), sizeof(Node)); if (nodes == NULL) { exit(0); } } int play(void) { int c; while ((c = getchar()) != 'q') { if (c == 'n') { width = height = 15; /* new game maze dimensions */ printf("new game\n"); #if 0 free(nodes); nodes = NULL; #endif return 1; } } return 0; } 

Run with 'valgrind -s --leak-check=full ./free' and then enter 'n' (for new game), followed by 'q' (to quit).

With the #if 0/#endif block in play(), valgrind returns "LEAK SUMMARY: definitely lost: 800 bytes in 1 blocks".

With #if 1/#endif, valgrind returns no leaks but in my actual game, valgrind gives errors like "Invalid read size of 1" and "Address 0x4c49865 is 7,765 bytes inside a block of size 58,144 free'd".

After searching here on stackoverflow, I learned I am seeing these errors because I am calling calloc() twice for 'nodes' and/or using calloc() after free(). I have tried experimenting with an 'if' loop to initialize only once and also attempted to use realloc(), but I run into other problems, like 'nodes' isn't the correct size or it retains the data from the initial game.

My question is this: Is there a correct way to re-initialize and clear 'nodes' using the new game maze dimensions? Also, when and where should I be using free(nodes)?

9
  • If I switch #if 0 to #if 1 it runs clean for me under valgrind. Are you sure that's the code that gives you the "invalid read" error? Commented Sep 19, 2023 at 18:44
  • @dbush, yes, the example here runs clean under valgrind, but in my actual game, this block of code triggers the valgrind errors. i tried to simplify the problem here in my example, assuming i've just done something wrong with the way i'm trying to re-initialize the maze when 'new game' is selected. if this example is correct, then i will have to troubleshoot my actual game code some more Commented Sep 19, 2023 at 18:50
  • 2
    Don't simplify it so much that the problem goes away. We need an example that demonstrates the problem. Commented Sep 19, 2023 at 19:15
  • Some side issues: (size_t)(width * height) better as (size_t)width * height to less likely overflow. Node *nodes; --> Node *nodes = NULL; as NULL is not certainly a zero pattern. while ((c = getchar()) != 'q') { risks an infinite loop. Better as while ((c = getchar()) != 'q' && c != EOF) {. Commented Sep 19, 2023 at 19:27
  • 1
    @rsarson A good way to use dynamic data is some_type *ptr = NULL or malloc/calloc(...); later free(ptr); ptr = NULL or malloc/calloc(...); (repeat as needed), finally free(ptr); ptr = NULL;. Note that it is OK to call free(ptr); even if ptr == NULL. Commented Sep 20, 2023 at 1:06

2 Answers 2

0

Probably the easy --- and safe --- way to go is to write the code around your data and encapsulate Maze as you could do in any more object-oriented languages.

Below is a complete example, with comments and output, of an alternative way of writing this kind of code.

from the original code

 void init(void); int play(void); typedef struct Node { int x, y; } Node; /* global variables */ Node* nodes; int width, height; 

In data structures in general when you see a Node thing there is some object around it, an array, a linked list, a map, a set, whathever.

But the node is not the structure and the structure is not a node. A structure is a (possibly empty) collection of nodes. And the nodes points to --- or contains --- data, a unit of data per node.

In your case the structure is a Maze, an array of Nodes. The data is a pair of int.

But you have no struct for a Maze. Failure on doing so takes your representation far from the model itself. This will make the code hard to implement, to test and to change.

You have a single pointer Node*, a global one, and 2 global int. This is fragile. And play relies on a single global pointer and returns nothing: more and more problems.

An alternative: Maze as an object

typedef struct { int x; int y; } Node; typedef struct { unsigned width; unsigned height; Node* nd; } Maze; 

This is just an example, and I will left an implementation around this, in order to illustrate the way many people write these things

some operations on Maze

Maze* so_create(unsigned W, unsigned H); Maze* so_destroy(Maze* maze); int so_display(Maze* maze, const char* title); Node* so_get_node(unsigned x, unsigned y, Maze* maze); Maze* so_resize( Maze** original, unsigned new_w, unsigned new_h); // shows a single node int so_show_node(Node* node, const char* msg); // helper: just number all maze nodes int so_fill_maze(Maze* maze); int so_play(Maze* maze); // does nothing here 

This way play() could be int so_play(Maze* maze) and you can see one of the differences: it becomes easy to play dozens of mazes on the same code.

A very simple implementation for these functions are below. They are minimal, with 5 to 10 lines each, and serves only as illustration.

  • so_create(w,h) returns a new Maze with the required size
  • so_destroy() returns NULLand destroys the maze, deallocating the memory
  • so_display() shows a maze on screen so the program can test itself. And a message can be inserted so we will not need to insert dozens of calls to printf()
  • so_get_node returns a new Node with the values of the one in the specified position in the Maze, or NULL
  • so_resize() does the obvious, but here it copies nodes were possible, just for fun and testing
  • so_show_node prints a single node. This is to make life easier if we want to customize the way each node is printed. The version in the example here uses the format (xxx,yyy)
  • so_fill_maze load each Node in Maze with its position, so we can see on screen how things are going.
  • so_play does nothing and it is just as the alternative to void play(void)

main for a test run

This code is self-explanatory but:

  • a 2x4 maze is created
  • it is displayed we expect all nodes (0,0)
  • fill_maze is called, numbering all nodes with its position
  • the Maze is displayed again
  • the maze is resized and displayed again
  • a node at a certain position is consulted
  • the data is freed
#include "stuff.h" int main(void) { Maze* tst = so_create(2, 4); so_display(tst, "2x4 just created"); so_fill_maze(tst); so_display(tst, "\nsame one, after fill"); // tst is destroyed inside so_resize() Maze* new_m = so_resize(&tst, 5, 5); so_display(new_m, "\nresize from maze above"); so_play(new_m); Node* node = so_get_node(1,3, new_m); so_show_node(node, "node at (1,3) is "); free(node); new_m = so_destroy(new_m); // free last return 0; } 

output for this test

2x4 just created [2,4] Maze ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) same one, after fill [2,4] Maze ( 0, 0) ( 1, 0) ( 0, 1) ( 1, 1) ( 0, 2) ( 1, 2) ( 0, 3) ( 1, 3) resize from maze above [5,5] Maze ( 0, 0) ( 1, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 1) ( 1, 1) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 2) ( 1, 2) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 3) ( 1, 3) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) ( 0, 0) play(5,5) node at (1,3) is ( 1, 3) 

The code for the example

a header file stuff.h

#pragma once #include <memory.h> #include <stdio.h> #include <stdlib.h> typedef struct { int x; int y; } Node; typedef struct { unsigned width; unsigned height; Node* nd; } Maze; Maze* so_create(unsigned W, unsigned H); Maze* so_destroy(Maze* maze); int so_display(Maze* maze, const char* title); Node* so_get_node(unsigned y, unsigned x, Maze* maze); Maze* so_resize( Maze** original, unsigned new_w, unsigned new_h); // shows a single node int so_show_node(Node* node, const char* msg); // helper: just number all maze nodes int so_fill_maze(Maze* maze); int so_play(Maze* maze); // does nothing here 

the functions in stuff.c

#include "stuff.h" Maze* so_create(unsigned W, unsigned H) { Maze* one = (Maze*)malloc(sizeof(Maze)); if (one == NULL) return NULL; one->width = W; one->height = H; size_t size = (size_t)W * (size_t)(H) * sizeof(Node); one->nd = (Node*)malloc(size); if (one->nd == NULL) { free(one); return NULL; } memset(one->nd, 0, size); // clear nodes return one; } Maze* so_destroy(Maze* one) { if (one == NULL) return NULL; free(one->nd); free(one); return NULL; } int so_display(Maze* maze, const char* msg) { if (maze == NULL) return -1; // an optional message can be useful if (msg != NULL) printf("%s", msg); printf( " [%u,%u] Maze\n\n", maze->width, maze->height); Node* p = maze->nd; for (unsigned h = 0; h < maze->height; h += 1) { for (unsigned w = 0; w < maze->width; w += 1) so_show_node(p++, NULL); printf("\n"); } return 0; } Node* so_get_node(unsigned x, unsigned y, Maze* maze) { if (maze == NULL) return NULL; if (maze->width < x) return NULL; if (maze->height < y) return NULL; // ok, we have maze and point (x,y) is ok Node* node = malloc(sizeof(Node)); if (node == NULL) return NULL; *node = *(maze->nd + y * maze->width + x); return node; } Maze* so_resize( Maze** original, unsigned new_w, unsigned new_h) { // original is Maze** so we can reset the pointer // here Maze* one = so_create(new_w, new_h); if (one == NULL) return NULL; unsigned lim_w = new_w < (*original)->width ? new_w : (*original)->width; unsigned lim_h = new_h < (*original)->height ? new_h : (*original)->height; Node* p_new = one->nd; Node* p_old = (*original)->nd; // copy nodes to new maze were possible for (unsigned h = 0; h < lim_h; h += 1) for (unsigned w = 0; w < lim_w; w += 1) *(one->nd + h * one->width + w) = *((*original)->nd + h * (*original)->width + w); *original = so_destroy(*original); return one; } int so_show_node(Node* node, const char* msg) { if (node == NULL) return -1; // an optional message can be useful also here if (msg != NULL) printf("%s", msg); printf("(%3d,%3d) ", node->x, node->y); return 0; } int so_fill_maze(Maze* maze) { // each node will have (x,y) = the node position // in maze, just for testing if (maze == NULL) return -1; // no maze Node* p = maze->nd; for (unsigned h = 0; h < maze->height; h += 1) for (unsigned w = 0; w < maze->width; w += 1) p->x = w, p->y = h, p++; return 0; } int so_play(Maze* maze) { if (maze == NULL) return -1; printf("play(%u,%u)\n", maze->width, maze->height); return 0; } 

main is just above in the comments.

HIH

Sign up to request clarification or add additional context in comments.

Comments

0

The problem is indeed that you end up calling calloc multiple times in a loop and if you don't free(nodes) in between, then that's a memory leak.

My question is this: Is there a correct way to re-initialize and clear 'nodes' using the new game maze dimensions? Also, when and where should I be using free(nodes)?

Since you allocated the whole thing in a single calloc call, you essentially have a "flat" 2D array with everything allocated in adjacent cells.

The easiest fix would be move calloc before the loop, move free after the loop and reset the data using memset(nodes, 0, width * height * sizeof(Node)).

However, why are you using dynamic allocation in the first place? The dimensions are known at compile-time. Something like this would be faster and less error prone all at once:

#define width 10 #define height 10 int main(void) { static Node nodes[width][height]; ... 

static to avoid having the array allocated on the stack.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.