C- How to properly access and deallocate memory used by a pointer of another struct.


So assuming I have two structs where game_t struct has a board_t type pointer to remember all the information about the board. The board member variable cells has dynamic memory via
 
board->cells = calloc(20 * 10, sizeof(int));

And I attempt to free this memory via
 
free(game->board->cells);


What is going wrong? (I am assuming I am accessing cells incorrectly somehow)

common.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  #ifndef COMMON_H
#define COMMON_H

#include <stddef.h>


// Bitflags enable us to store cell data in integers!
#define FLAG_PLAIN_CELL 0x0
#define FLAG_SNAKE 0x1
#define FLAG_WALL 0x2
#define FLAG_FOOD 0x4

/** Enumerated key input options:
 * INPUT_UP, INPUT_DOWN, INPUT_LEFT, INPUT_RIGHT, and INPUT_NONE
 */
typedef enum input_key {
    INPUT_UP,
    INPUT_DOWN,
    INPUT_LEFT,
    INPUT_RIGHT,
    INPUT_NONE
} input_key_t;



/** Struct containing board data.
 *
 * Fields:
 * - width: the width of the board.
 * - height: the height of the board.
 * - cells: a pointer to the first integer in an array of integers representing
 * each board cell.
 */
typedef struct board {
    size_t width;
    size_t height;
    int* cells;  // array of integers containing data re each cell via bitmap
    snake_t* snake;
} board_t;

/** Struct containing game data.
 *
 * You may need to modify this in part 2 of the project!
 *
 * Fields:
 * - game_over: if the game is over, then 1; else, 0.
 * - score: current game score. Starts at 0.
 * - board: a pointer to the game's board struct
 */
typedef struct game {
    int game_over;   // 1 if game is over, 0 otherwise
    int score;       // game score: 1 point for every food eaten
    board_t* board;  // pointer to the board struct for this game
} game_t;



#endif 


game_setup.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "game_setup.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Some handy dandy macros for decompression
#define E_CAP_HEX 0x45
#define E_LOW_HEX 0x65
#define S_CAP_HEX 0x53
#define S_LOW_HEX 0x73
#define W_CAP_HEX 0x57
#define W_LOW_HEX 0x77
#define DIGIT_START 0x30
#define DIGIT_END 0x39

/* Initializes the board struct with walls around the edge of the board.
 * Returns NULL on failure; otherwise returns the pointer to the modified board.
 * Arguments:
 *  - board: a pointer to a board_t
 *  - width: a positive int equal to the desired outer width
 *  - height: a positive int equal to the desired outer height
 */
board_init_status_t initialize_default_board(board_t* board) {
    board->width = 20;
    board->height = 10;
    board->cells = calloc(20 * 10, sizeof(int));

    // Set edge cells!
    // Top and bottom edges:
    for (int i = 0; i < 20; ++i) {
        board->cells[i] = FLAG_WALL;
        board->cells[i + (20 * (10 - 1))] = FLAG_WALL;
    }
    // Left and right edges:
    for (int i = 0; i < 10; ++i) {
        board->cells[i * 20] = FLAG_WALL;
        board->cells[i * 20 + 20 - 1] = FLAG_WALL;
    }

    // Add snake
    board->cells[20 * 2 + 2] = FLAG_SNAKE;

    return INIT_SUCCESS;
}

/** Initialize the game struct, with a (potentially-empty) board representation.
 * Arguments:
 *  - `game`: a pointer to the game struct to be initialized.
 *  - `board_rep`: a string representing the initial board. May be empty.
 */
board_init_status_t initialize_game(game_t* game, char* board_rep) {
    // TODO: implement!
    game->game_over = 0;
    game->score = 0;
    initialize_default_board(game->board);
    return INIT_SUCCESS;
}


game.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "game.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "linked_list.h"
#include "mbstrings.h"

/** Sets a random space on the given board to food.
 * Arguments:
 *  - `board`: a pointer to the board to be updated.
 */
void place_food(board_t* board) {

    int food_index = generate_index(board->width * board->height);
    if (*(board->cells + food_index) == FLAG_PLAIN_CELL) {
        *(board->cells + food_index) = FLAG_FOOD;
    } else {
        place_food(board);
    }
    
}



* Cleans up on game over — should free any allocated memory so that the
 * LeakSanitizer doesn't complain.
 * Arguments:
 *  - `game`: a pointer the current game struct.
 */
void teardown(game_t* game) {
   
    free(game->board->cells);
}
 



snake.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#define _XOPEN_SOURCE_EXTENDED 1
#include <curses.h>
#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "game.h"
#include "game_over.h"
#include "game_setup.h"
#include "mbstrings.h"
#include "render.h"

/** Gets the next input from the user, or returns INPUT_NONE if no input is
 * provided quickly enough.
 */
input_key_t get_input() {
    /* DO NOT MODIFY THIS FUNCTION */
    int input = getch();

    if (input == KEY_UP) {
        return INPUT_UP;
    } else if (input == KEY_DOWN) {
        return INPUT_DOWN;
    } else if (input == KEY_LEFT) {
        return INPUT_LEFT;
    } else if (input == KEY_RIGHT) {
        return INPUT_RIGHT;
    } else {
        // if the input isn't an arrow key, we treat it as no input (could add
        // other keys in if we want other commands, like 'pause' or 'quit')
        return INPUT_NONE;
    }
    /* DO NOT MODIFY THIS FUNCTION */
}

/** Helper function that procs the GAME OVER screen and final key prompt.
 */
void end_game(game_t* game) {
    // Game over!

    // Free any memory we've taken
    teardown(game);
    //free(game->board->cells);
    
  
    // tell ncurses that we're done
    endwin();
}

int main(int argc, char** argv) {
    // Main program function — this is what gets called when you run the
    // generated executable file from the command line!

    // initialize board from command line arguments
    game_t game;
    board_t board;
    snake_t snake;
    game.board = &board;
    board.snake = &snake;
    int snake_grows;

    switch (argc) {
        case (2):
            snake_grows = atoi(argv[1]);
            if (snake_grows != 1 && snake_grows != 0) {
                printf(
                    "snake_grows must be either 1 (grows) or 0 (does not "
                    "grow)\n");
                return 0;
            }
            initialize_game(&game, NULL);
            break;
        case (3):
            snake_grows = atoi(argv[1]);
            if (snake_grows != 1 && snake_grows != 0) {
                printf(
                    "snake_grows must be either 1 (grows) or 0 (does not "
                    "grow)\n");
                return 0;
            } else if (*argv[2] == '\0') {
                initialize_game(&game, NULL);
                break;
            }
            initialize_game(&game, argv[2]);
            break;
        case (1):
        default:
            printf("usage: snake <GROWS: 0|1> [BOARD STRING]\n");
            return 0;
    }

    /*
    while (1) 
    {
        usleep(100);
        update(game, none, snake_grows);
        render_game(game_t * game)
    }
    end_game(&game);
    */
}



Error
1
2
3
4
5
6
7
8
9
10
==497==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 800 byte(s) in 1 object(s) allocated from:
    #0 0x7f3f5430edc6 in calloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10ddc6)
    #1 0x55e228513927 in initialize_default_board src/game_setup.c:27
    #2 0x55e228513c7f in initialize_game src/game_setup.c:56
    #3 0x55e228514b4e in main src/snake.c:81
    #4 0x7f3f53fcc0b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)

SUMMARY: AddressSanitizer: 800 byte(s) leaked in 1 allocation(s).
Last edited on
Show more context for where you call malloc/calloc/etc.
Is board also being dynamically allocated?

<edited: I wrote wrong variable before this>
Last edited on
Updated!
Last edited on
Perhaps you forgot to call teardown entirely.

Post enough code to reproduce the problem.
Sorry, I still don't see any issue in the additional code.
I posted the teardown call if it is not enough I will add more.
You never called end_game. It is commented out.

You could have found this issue on your own by using the debugger.
You could have checked your assumption that it was actually called when I suggested it in my prior post.

If none of those things led you to a solution, you should have removed stuff from your program until you couldn't remove any more without solving your problem.

If this still didn't lead you to a solution (and it was very likely do so), you should have posted this small, complete, compilable reproduction of your problem in the forum.
Last edited on
Thanks for the help not sure how such a simple issue got passed me.
They haven't taught us to use debuggers yet and I hesitated to include the full file because there were a lot of files.
For a minimal reproducible example, there are a lot of files and I didn't even really understand the issue so I was unable to really come up with one.
Apologies for the issues.
should be the second week or so but is often not even included at all!

a modern debugger, like visual studio, is fairly simple to use.
the most basic approach is:
1) compile it in debug mode. this lets you see the source code, line by line and variable by variable as you execute the program.
2) run it in debug mode, from your IDE.
3) put a 'break point' where you think there is an issue.
4) when the program stops running at your break point, look at the values of the variables for problems. you can use a 'watch'ed variable to quickly step code until the value changes. You can set conditions on the break point, so it stops when you get a weird value in a specific variable (for an example) so you can try to see where that came from.

your mental process is something like:
The program does something stupid here. Why? What values in these 15 variables is causing that to happen? ... stop the code when it happens, look at the values ... O, I see, x = b/c and c is zero this time around! ... /fix
They haven't taught us to use debuggers yet

A debugger is not a panacea, just a tool to help you check your assumptions. Simply knowing how to use one isn't enough. You also need to know when to use one and what to look for. The best way that I know of to learn this is by watching a professional do it.

For a minimal reproducible example, there are a lot of files and I didn't even really understand the issue so I was unable to really come up with one.

First you take a backup of your source code. Then you just rip functionality out of your program a little bit at a time, testing after each removal, until proceeding further would quiet Valgrind. The last program you end up with is the minimal example.

This approach is useful basically because you don't need to understand the problem to use it. Instead you'll narrow in on it iteratively.
Last edited on
Topic archived. No new replies allowed.