4

I instantiated a two dimensional array in a double int pointer by the following methodology.

int **make2Dint(int rows, int columns) {
    //a is our 2D array
    int **a = (int **)calloc(rows, sizeof(int *));
    //If we can't allocate the necessary memory, return null.
    if (!a) {
        return (int **)NULL;
    }

    //Allocate each row with the correct amount of column memory.
    for (int i = 0; i < rows; i++) {
        a[i] = (int *) calloc(columns, sizeof(int));
        //If we can't allocate the necessary memory, return null.
        if (!a[i]) {
            return (int **)NULL;
        }
    }

    return a;
}

For this, I also wrote a free function, which is as follows

void free2DArray(void **a, int rows) {
    for (int i = 0; i < rows; i++) {
        free(a[i]);
    }

    free (a);
}

When going to use my free function, (by way of free2DArray(array, rows); however, gcc gave me a warning.

In file included from life.c:6:0:
twoD.h:14:6: note: expected ‘void **’ but argument is of type ‘int **’
 void free2DArray(void **a, int rows);
      ^~~~~~~~~~~

Now, I can make this go away with a cast to void **, but this seems to indicate that my usage of void ** is problematic.

There is no generic pointer-to-pointer type in C. void * acts as a generic pointer only because conversions (if necessary) are applied automatically when other pointer types are assigned to and from void *'s; these conversions cannot be performed if an attempt is made to indirect upon a void ** value which points at a pointer type other than void *. When you make use of a void ** pointer value (for instance, when you use the * operator to access the void * value to which the void ** points), the compiler has no way of knowing whether that void * value was once converted from some other pointer type. It must assume that it is nothing more than a void *; it cannot perform any implicit conversions.

In other words, any void ** value you play with must be the address of an actual void * value somewhere; casts like (void **)&dp, though they may shut the compiler up, are nonportable (and may not even do what you want; see also question 13.9). If the pointer that the void ** points to is not a void *, and if it has a different size or representation than a void *, then the compiler isn't going to be able to access it correctly.

However, since I'm not dereferencing this pointer, I'm not sure that it's problematic. Can someone explain whether or not it's problematic, and why?

Community
  • 1
  • 1
ollien
  • 4,418
  • 9
  • 35
  • 58
  • 1
    I used to make this sort of mistake too, but just `void*` is a better way than `void**` to represent something that might for example be either a `int**` or `double**`. "I'm not dereferencing" - yes you are: `a[i]` means `*(a+i)`, so you are dereferencing a `void**` pointer which doesn't actually point at a `void*`. – aschepler Jan 27 '18 at 01:48
  • Let me rephrase that. I'm not dereferencing the value that the second pointer is pointing to (i.e. the integer). With that in mind, is it still a problem? I'll edit the question to be more specific. – ollien Jan 27 '18 at 01:51
  • Casting `void *` in c, is discouraged. But I've never seen anyone cast `NULL`! That said, your function leaks memory. – Iharob Al Asimi Jan 27 '18 at 01:53
  • 1
    Why not simply have a `free2Dint()` and a `free2Ddouble()` with the different parameter types (`int **` vs `double **`) and the rest of the code with the same text? The amount of code is minimal; the confusion is minimal. Strictly, there are limits on the compatibility of pointers, but in practice, you're extremely unlikely to be on a platform where the theoretical becomes practical. – Jonathan Leffler Jan 27 '18 at 01:54
  • @IharobAlAsimi Can you elaborate how this leaks memory? I'm not sure that I see it. – ollien Jan 27 '18 at 01:56
  • In your allocator, you have: `if (!a[i]) { return (int **)NULL; }` when an intermediate (or maybe I should use 'inner') allocation fails. To prevent leaks, you need: `if (!a[i]) { for (j = 0; j < i; j++) free(a[j]); free(a); return (int **)NULL; }`. That releases the previously allocated intermediate arrays and the outer array, all of which were allocated and leaked in your existing code. – Jonathan Leffler Jan 27 '18 at 01:56
  • @JonathanLeffler That leak explanation makes sense. Thanks. With regards to the multiple functions, this was more of a pet curiosity than something I was practically using. I'm more curious as to why it would invalid than anything. Can you elaborate on these "compatibility limitations"? – ollien Jan 27 '18 at 01:59
  • 1
    Not until I get to a machine with a copy of the standard on it. You can read [C11 §6.2.5 Types ¶28](http://port70.net/~nsz/c/c11/n1570.html#6.2.5p28) and nearby paragraphs to see what I'm talking about — it makes guarantees for `void *` and `char *` (various variants), and for stucture pointers and union pointers, but not for other mixed pointer types. POSIX makes stronger requirements. – Jonathan Leffler Jan 27 '18 at 02:00
  • 4
    In the C language, different kinds of pointers can have different representations. You could for example have a situation where int* = 4 bytes and void* = 8 bytes (and there have been machines where this was the case). Same representation is guaranteed for groups of types: void* and all char* variants. All int* variants, long*, double* etc. All struct* variants. All union* variants. – gnasher729 Jan 27 '18 at 02:04
  • [don't cast the result of malloc in C](https://stackoverflow.com/q/605845/995714) – phuclv Jan 27 '18 at 02:22

2 Answers2

2

No. Even if the pointer types have the same size and representation, C does not permit distinct types to alias except in a few special cases, and compilers can and will make transformations (optimizations) assuming this. If you really want such a generic free loop, you can put the loop in a macro that expands in the context where it has the right pointer type (in the caller) but you should really just avoid "deep" pseudo 2D arrays

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
1

Creating 2D arrays as nested 1D arrays is bad practice. You should allocate the entire space in a single step, and free() it just once.

Not only will this entirely sidestep your current issue, it will give you superior performance for rectangular (not jagged) 2D arrays, because all the memory will be contiguous, rather than scattered by row/column.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Could you elaborate how you would go about this? – ollien Jan 27 '18 at 02:20
  • 1
    @ollien the use of pointer to pointer needs a lot of allocation for each row, needs 2 indirections and would be slower. Use a single array and calculate the index manually https://stackoverflow.com/a/28841507/995714 – phuclv Jan 27 '18 at 02:27
  • 1
    You don't need to calculate index manually; just use pointer-to-VLA types and allocate a genuine array of arrays. – R.. GitHub STOP HELPING ICE Jan 27 '18 at 12:56