1

I wrote a function with three parameter as height, width, and depth. After create a 3-D array with malloc, I return a unsigned*** as array’s name.

The other function is meant to delete this dynamic 3-D array. Using array’s name as parameter, and free it in the function.

But I got some RE and MLE in the submit, I wonder where shouldn’t I do?

unsigned ***new_3d_array(unsigned n, unsigned m, unsigned k)
{
    unsigned ***arr3D = (unsigned***)malloc(n * sizeof(unsigned**));
    int i, j;

    for (i = 0; i < n; i++)
    {
        arr3D[i] =  (unsigned**)malloc(m * sizeof(unsigned*));
        for (j = 0; j < m; j++)
        {
            arr3D[i][j] = (unsigned*)malloc(k * sizeof(unsigned));
        }
    }

    return arr3D;
}
void delete_3d_array(unsigned ***arr)
{
    int i = 0, j = 0;
    while (arr[i] != NULL)
    {
        j = 0;

        while (arr[i][j] != NULL)
        {
            free(arr[i][j]);
            j++;
        }
        free(arr[i]);
        i++;
    }
    free(arr);
}
niorix
  • 65
  • 6
  • What are the maximum values for n, m and k? MLE means "memory limit exceeded", so maybe your code actually tries to allocate too much memory? – Denis Sheremet Nov 20 '19 at 11:07
  • Do you actually append NULL as sentinel value to the various arrays? You don't show how you initialize them. – Lundin Nov 20 '19 at 11:24
  • @Lundin - I like the "sentinel" term! Can I 'borrow' it in my answer? – Adrian Mole Nov 20 '19 at 11:25
  • @Adrian-ReinstateMonica Yes you may use any commonly used term in computer science in your answers... https://en.wikipedia.org/wiki/Sentinel_value – Lundin Nov 20 '19 at 11:26

3 Answers3

1

In your delete_3d_array function, you rely on the first 'out-of-bounds' pointer elements of arr[i] and arr[i][j] being NULL - but this is not safe, unless you explicitly add these 'extra' values (sentinels) in your new_3d_array function and assign NULL to these:

unsigned ***new_3d_array(unsigned n, unsigned m, unsigned k)
{
    unsigned ***arr3D = malloc((n+1) * sizeof(unsigned**)); /// Add space for a NULL marker.
    unsigned i, j; /// As suggested by chux - Makes more sense to avoid mixing types.

    arr3D[n] = NULL; /// Explicitly set the 'end marker' to NULL
    for (i = 0; i < n; i++)
    {
        arr3D[i] = malloc((m+1) * sizeof(unsigned*)); /// Again, add end-marker...
        arr3D[i][m] = NULL; // ... and definitively set it to NULL!
        for (j = 0; j < m; j++)
        {
            arr3D[i][j] = malloc(k * sizeof(unsigned));
        }
    }

    return arr3D;
}

Also, see here on casting the return value of malloc.

EDIT/APPENDIX:
The above code will fix your RE problem, assuming that RE is "Run-Time Error" (that is, de-referencing an invalid/uninitialized pointer). However, there is still a problem caused by multiple calls to malloc (or calloc), known as "heap fragmentation." Each of your innermost loop's malloc calls will ask the operating system to 'look for' space to assign to the requested memory, and this can (especially if your m dimension is large) make the O/S 'think' it's run out of memory.

You can help prevent this from happening by calling malloc much less often (and for larger blocks), and then calculating (rather than allocating) your 2nd-level pointers:

unsigned*** new_3d_array(unsigned n, unsigned m, unsigned k)
{
    unsigned*** arr3D = malloc((n + 1) * sizeof(unsigned**)); // Add space for a NULL marker.
    unsigned i, j;
    arr3D[n] = NULL; /// Explicitly set the 'end marker' to NULL
    for (i = 0; i < n; i++) {
        arr3D[i] = malloc(m * sizeof(unsigned*));
        unsigned* block = malloc(sizeof(unsigned) * m * k); // Allocate one BIG block of m x k 
        for (j = 0; j < m; j++)
            arr3D[i][j] = block + j * m;// First element (j == 0) will be "block", others will be incremented by "m"
    }
    return arr3D;
}

Of course, you would then need to modify your delete_3d_array function accordingly:

void delete_3d_array(unsigned*** arr)
{
    int i = 0;
    while (arr[i] != NULL) {
        free(arr[i][0]); // This will free the WHOLE BLOCK (m x k) allocated above!
        free(arr[i]);
        i++;
    }
    free(arr);
}

You could, in theory, even change the 'outer' loop in a similar way; but then, the syntax for calculating pointer addresses for the sub-arrays becomes quite arcane.

Feel free to ask for any further clarification and/or explanation.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • "you rely on the first 'out-of-bounds' pointer elements of arr[i] and arr[i][j] being NULL - but this is not safe!" We don't know that. If NULL is always appended as sentinel value, such code is safe. Since we don't know, we can't answer the question. – Lundin Nov 20 '19 at 11:25
  • 1
    @Lundin - In the code presented by the OP, no 'sentinel' is added, so it is unsafe (UB). – Adrian Mole Nov 20 '19 at 11:26
1

I believe better to use calloc, instead of malloc. Easier interface, and reduces the possibility of incorrect initialization.

Style note: better to use for loops, instead of while loops to free the data.

unsigned ***new_3d_array(unsigned n, unsigned m, unsigned k)
{
    unsigned ***arr3D = calloc(n+1, sizeof(*arr3d));
    for (int i = 0; i < n; i++)
    {
        arr3D[i] =  calloc(m+1, sizeof(*arr3D[i]));
        for (int j = 0; j < m; j++)
        {
            arr3D[i][j] = calloc(k, sizeof(*arr3D[i][j]));
        }
    }

    return arr3D;
}
void delete_3d_array(unsigned ***arr)
{
    for (int i=0 ; arr[i] != NULL ; i++ ) {
        for (int j = 0; arr[i][j] != NULL ; j++) {
            free(arr[i][j]);
        }
        free(arr[i]);
        arr[i] = NULL ;
    }
    free(arr);
}
dash-o
  • 13,723
  • 1
  • 10
  • 37
  • 1
    Good use of `sizeof` object vs type! Unclear why retain the unneeded casts like `(unsigned***)` when promoting style and mixed types in `i < n`. – chux - Reinstate Monica Nov 20 '19 at 12:39
  • A nice answer! However, although using `calloc` *will* set the 'extra' *(sentinel)* pointers to zero, I still think that lines like `arr3D[n] = NULL;` make it much clearer to others reading the code what you are using this last element for (especially with a comment to that end). Also, `calloc` will set *all* elements to zero, when most are going to be replaced - so there may be a small performance penalty. – Adrian Mole Nov 20 '19 at 12:51
  • The major performance penalty here is the heap fragmentation, with allocation all over the place. calloc overhead is negligible compared to that major bottleneck. – Lundin Nov 20 '19 at 12:54
  • @Lundin On the "heap fragmentation" issue, see my added EDIT/APPENDIX. – Adrian Mole Nov 20 '19 at 15:00
  • @Adrian-ReinstateMonica You are making things needlessly complicated. Calculating indices is obsolete C89 style. In modern C simply use an array pointer to VLA and let the compiler do the work. Also note that the heap fragmentation in itself isn't the real problem (in itself it will only lead to wasted memory), but rather the lack of coherence for data cache during access. I've written a FAQ post about it here: [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays). – Lundin Nov 20 '19 at 15:22
  • @Lundin OK, I'll accept the 'lesson'! As a (mostly) `C++` programmer (`C` in the old days), I have an inherent 'allergy' to VLAs. – Adrian Mole Nov 20 '19 at 15:26
  • 1
    @Adrian-ReinstateMonica That's ok, as a (mostly) C programmer (C++ in the old days), I'm allergic to some 90% of the C++ language :) VLAs are quite useless. Pointers to VLA are great. For example consider `void stuff (int x, int y, int matrix[x][y])`. Can't do that in C++. – Lundin Nov 20 '19 at 15:50
0

@adrian has given one possible solution.

There is another way by passing the size of the array to the delete function. You only need to pass n and m to the function.

void delete_3d_array(unsigned ***arr, int n, int m)
{
    int i,j;
    for (i=0; i<n; i++)
    {
        for (j=0; j<m; j++)
        {
            free(arr[i][j]);
        }
        free(arr[i]);
    }
    free(arr);
}
Rishikesh Raje
  • 8,556
  • 2
  • 16
  • 31