0

I'm trying to build a data structure, using the C language, which allows me to record strings (representing altitude values) whose data describe some characteristics of points in a map. Each point should have, in addition to the characteristics described by the corresponding string, also a coordinate corresponding to the longitude (column) and one corresponding to the latitude (row).

I would like to output this map as an empty structure by allocating memory in the heap for the rows and columns. The idea is that you have a pointer to a pointer to a pointer to char, for which you allocate memory for an array of pointers to pointers to char, each pointing to the beginning of a row. Each pointer to pointer to char contained in the first array must then have memory allocated for an array of pointers to char.

This will be the basic structure. There is no need at this stage to allocate strings for each of the pointers to char, because the strings will be constructed by reading a text file and then "pointed" to by assigning the addresses corresponding to their beginning. I've tried allocating memory in such a way that pointer arrays are treated as NULL-terminated arrays of pointers, i.e. using a null pointer instead of a nul-byte to define the end of the row array and the end of each series of points on the columns. Based on tests, this strategy worked well: using pointer arithmetic (ie not indexing the arrays with counters) and two integer counters, it resulted in an exact number of rows and, for each row, an exact number of columns.

However, when I have to free memory instead, Valgrind warns me that my code is producing an invalid free of some memory blocks.

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -s -v ./the_program

Is the Valgrind's invocation I used.

The following is the code: can anyone see my error? What am I missing?

int allocate_memory_for_map_structure(void) // The real function (but what is
                                            // reality?) takes a pointer to some
                                            // data stored in the data segment,
                                            // e.g. values for "width" and
                                            // "height".
{
    char    ***map;
    char    ***swap;
    char    ***swap2;
    char    ***start;
    int     count;
    int     width;
    int     height;

    width = 100; // could be any integer value passed to the function
    height = 75; // same as above
    map = malloc((height + 1) * sizeof(*map));
    if (map == NULL)
        return (-1);
    count = 0;
    swap = map;
    while (count < height){
        *swap = malloc((width + 1) * sizeof(**swap));
        if (*swap == NULL)
            return (clean_lines_in_map_structure(map));
        swap++;
        count++;
    }
    *swap = NULL;
    /*
    test_outcome_of_allocation(map); // I will not add this function's code to the
                                     // question. However, it was a simple test
                                     // that correctly counted the number of rows and
                                     // columns stoppin iterations when NULL
                                     // was reached by dereferencing incremented pointers.
    */
    start = map;
    swap2 = map;
    while (*swap != NULL){
        map = swap;
        swap++;
        free(*map);
    }
    free(start);
    return (0);
}

I'm interested in understanding how to do it, but even more I'm interested in understanding my mistake: why what I wrote causes an invalid freeing of memory?

  • `map = swap` will not work. Remember that in C all arguments are passed *by value*. That means the value in the call is copied into the functions local argument variable. Once the function returns, that current value of that local argument variable is lost, together with all other local variables. – Some programmer dude Aug 22 '23 at 07:12
  • And for a dynamically allocated two-dimensional "rows and columns" container, you need a pointer to a pointer, e.g. `char **map;`. – Some programmer dude Aug 22 '23 at 07:14
  • Furthermore, the variable `swap` is of type `char ***`. So `**swap` will have the type `char *`. And the size of a pointer is the size of the pointer itself, not to what it might point to. So `sizeof(**swap)` is very likely not correct. – Some programmer dude Aug 22 '23 at 07:15
  • Oh and note that for any pointer or array `map`, the expression `*map` is *exactly* equal to `map[0]`. Which also means that `**swap` is the same as `swap[0][0]`. – Some programmer dude Aug 22 '23 at 07:16
  • Thank you @some-programmer-dude . I don't really understand your answers (please, be patient, since I am a beginner). I don't need to point the beginning of a string at this moment (the strings will be allocated later). So I only need to allocate memory for the pointers (i.e. sizeof **swap) that will point those beginning. – Federico Porciello Aug 22 '23 at 07:29
  • @some-programmer-dude I don't understand why you say `swap = map` won't work. The `map` variable has the value of an address, and this value is assigned to a `swap` variable of the same type as `map`. Why shouldn't it work? – Federico Porciello Aug 22 '23 at 07:37
  • What is the purpose of the `clean_lines_in_map_structure` function? Do you expect the `map` variable in `allocate_memory_for_map_structure` to have changed after the call to `clean_lines_in_map_structure`? – Some programmer dude Aug 22 '23 at 08:28
  • Shouldn't `if (*map == NULL)` be `if (*swap == NULL)`? – Ian Abbott Aug 22 '23 at 08:53
  • 1
    Please note that your pointer to pointer array is sometimes called *jagged 2D array*. If you want a *true* 2D array which can be handled with single malloc/free call, please read [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/q/42094465/694733). You will need a C compiler that is not completely old and obsolete. – user694733 Aug 22 '23 at 08:53
  • I tried to create a [mcve] from your code (which looks OK apart from the bug I mentioned above: after `*swap = malloc(...);` you wrote `if (*map == NULL)` instead of `if (*swap == NULL)`). Valgrind did not report any errors. Please post your own [mcve]. – Ian Abbott Aug 22 '23 at 09:09
  • I'm also worried about the `test_outcome_of_allocation` function. You say that it "counted the number of rows and columns stoppin iterations when NULL was reached by incrementing pointers". But that's not how pointers work. What pointer should be `NULL`? Pointers have no range, if you increment a pointer you just get a new pointer. You won't ever get a `NULL` pointer by just incrementing it (except if you overflow the pointer, which I doubt is well-defined behavior). – Some programmer dude Aug 22 '23 at 10:13
  • All in all, your knowledge of pointers, and possibly how function arguments works, seems to be based on a few misunderstandings. Please take some time to refresh the relevant chapters about those parts in your text-books. – Some programmer dude Aug 22 '23 at 10:15
  • @some-programmer-dude: 1) no, the `map` variable in `allocate_memory_for_map_structure` must not be changed by `clean_lines_in_map_structure`. – Federico Porciello Aug 22 '23 at 11:01
  • 2)in `test_outcome_of_allocation` I incremented a copy of `map` (created by simply passing `map` to the function) until `*copy_of_map == NULL` and, for each `*copy_of_map`, I incremented `*copy_of_map` until `**copy_of_map == NULL`. – Federico Porciello Aug 22 '23 at 11:09
  • Thank you @user694733 for the clarification. – Federico Porciello Aug 26 '23 at 20:05
  • @Ian Abbot: Thank you for the indication. However, the typo was not the problem: I created it during transcription. – Federico Porciello Aug 26 '23 at 20:11

1 Answers1

0

Using double pointers initialize and free the array entries is cumbersome and error prone. You should instead use the array syntax.

You seem to rely on null pointer sentinels for the matrix boundaries. This can work but passing the matrix dimensions seems more reliable.

Note that there is a typo in if (*map == NULL): it should be if (*swap == NULL).

Here is a modified version:

int clean_lines_in_map_structure(char ***map) {
    for (int i = 0; map[i]; i++) {
        free(map[i]);
    }
    free(map);
    return -1;
}

int allocate_memory_for_map_structure(void) // The real function (but what is
                                            // reality?) takes a pointer to some
                                            // data stored in the data segment,
                                            // e.g. values for "width" and
                                            // "height".
{
    int width = 100; // could be any integer value passed to the function
    int height = 75; // same as above
    char ***map = malloc(sizeof(*map) * (height + 1));
    if (map == NULL)
        return -1;
    for (int i = 0; i < height; i++) {
        map[i] = malloc(sizeof(*map[i]) * (width + 1));
        if (map[i] == NULL)
            return clean_lines_in_map_structure(map);
        // intialize the matrix entries
        for (int j = 0; j < width + 1; j++)
            map[i][j] = NULL;
    }
    map[height] = NULL;
    /* The following two instructions are here only for test. */
    test_outcome_of_allocation(map); // I will not add this function's code to the
                                     // question. However, it was a simple test
                                     // that correctly counted the number of rows and
                                     // columns stoppin iterations when NULL
                                     // was reached by incrementing pointers.
    clean_lines_in_map_structure(map);
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189