1

My teacher told me that int **array is not a 2D array, it is just a pointer to a pointer to an integer. Now, in one of my projects I have to dynamically allocate a 2D array of structs, and this is how it is done:

struct cell **array2d = (struct x **)calloc(rows, sizeof(struct x *));
    
for (int i = 0; i < columns; i++) {
    array2d[i] = (struct x *)calloc(j, sizeof(struct x));
}

But here we return a pointer to a pointer to the struct, so how is this a 2D array?

Before using dynamic allocation, I had a statically allocated array of the form: array2d[][]

Now that I replaced it by dynamic allocation, I also replaced array2d[][] by **array2d.
Every function that takes array2d[i][j] als argument now returns an error saying the types don't match.

Could someone explain me what is happening here? What is the difference between **array and array[m][n] and why is the compiler complaining?

Chris
  • 26,361
  • 5
  • 21
  • 42
Ronald
  • 157
  • 6
  • First of all, C doesn't have "2D" arrays. It does have nested arrays, arrays of arrays, which unfortunately are frequently talked about as multi-dimensional arrays. And secondly, an array of arrays is *not* the same as a pointer to a pointer (also sometimes known as a [*jagged array*](https://en.wikipedia.org/wiki/Jagged_array)). See e.g. [this old answer of mine](https://stackoverflow.com/questions/18440205/casting-void-to-2d-array-of-int-c/18440456#18440456) for a somewhat visual representation of the differences. – Some programmer dude Dec 08 '21 at 07:21
  • And to help you understand things a little better, there are two things you need to know: 1) All arrays (proper arrays) can *decay* to a pointer to its first element. So if we have e.g. `int array[X];` then using plain `array` is the same as `&array[0]`, with the type `int *`; And 2) For any array *or pointer* `p` and index `i`, the expression `p[i]` is *exactly* equal to `*(p + i)`, which means that all "array" indexing is really pointer arithmetic. These two things is what sometimes can make arrays and pointers seem similar. – Some programmer dude Dec 08 '21 at 07:23
  • 1
    Both or neither, depending on whom you ask. A 2D array is an abstraction. It only exist in people's heads (and details vary between people's heads). It is not a language construct. Not in C at any rate. Both `int **array` and `int array[m][n]` may or may not implement your preferred abstraction to some degree or another. But they are different, incompatible things as far as C is concerned. – n. m. could be an AI Dec 08 '21 at 07:31
  • `for (int i = 0; i < i; i++) {` - When do you expect `i` to be less than `i`? Will this loop ever run? Given that `i` appears before the loop, it looks like the `i` locally scoped to your list is shadowing the `i` in the outer scope. – Chris Dec 08 '21 at 07:39
  • 1
    @Chris I fixed it to make the example clearer. – Ronald Dec 08 '21 at 07:42
  • Duplicate: [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) – Lundin Dec 08 '21 at 07:44
  • @Someprogrammerdude I took a look at your answer, thank you. But what if the array elements themselves are pointers (to structs) and not just numbers? – Ronald Dec 08 '21 at 07:44
  • Style note: [Don't cast the result of malloc](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – Chris Dec 08 '21 at 07:47
  • @HeloKitay Then each element is a pointer, with an arrow to the structure. And if the structure have pointers, there's more arrows to whatever they point to. And so on. – Some programmer dude Dec 08 '21 at 07:47
  • @Someprogrammerdude so in't it in this case the same as **2darray? – Ronald Dec 08 '21 at 08:07

1 Answers1

3

They're thoroughly different things.

An array is a sequence of values of the same type stored one after another in memory.

In C, an array is more or less interchangeable with a pointer to its first elementa[0] is *a,
a[1] is *(a+1), etc. — at least when we're talking about one-dimensional arrays.

But now consider:

int a[3][3];

in this case, a contains nine elements, contiguous in memory. a[0][0] through a[0][2], then a[1][0] immediately after, up until a[2][2].

If you pass a to a function, it would fit into a parameter type of int * or int[3][3] or int [][3] (knowing the "stride" of the second dimension is absolutely necessary to doing the math to look up a given element).

On the other hand:

int *b[3];
b[0] = malloc(...);
b[1] = malloc(...);
b[2] = malloc(...);

in this case, b is an array of 3 elements, each of which is a pointer to an array of 3 elements. You still access it like b[0]0] or b[1][2], but something completely different is happening under the hood. The elements aren't all stored contiguously in memory, and *b isn't any of them, it's a pointer. If we were to pass b to a function, we would receive it with a parameter of type int ** or int *[]. Knowing the length of each row in advance isn't necessary, and in fact each row could have a different length from the others. Some of the rows could even be null pointers, with no storage behind them for integers.

hobbs
  • 223,387
  • 19
  • 210
  • 288
  • For your example `*a` is the same as `a[0]` -- you need `**a` to be the same as `a[0][0]`. The expression `a[1]` is perfectly meaningful; it is a pointer to the second subarray of `a` and will decay to `&a[1][0]` (a pointer to the first element of that subarray) in most cases. – Chris Dodd Dec 08 '21 at 08:05