0

I wrote the code -

int arr2d[2][2] = {{1, 2}, {3, 4}};
int * arr = (int*)arr2d;
int i = 0;
for(i = 0; i < 4; i++)
{
    printf("%d  ", arr[i]);
}

The output was as if I printed every element in the arrays in arr2d, nothing out of the ordinary. Why is that so?

anastaciu
  • 23,467
  • 7
  • 28
  • 53
shubbbik
  • 29
  • 4
  • can you show the output ? – Mickael B. May 02 '20 at 14:45
  • Why do you *want* to cast? – wildplasser May 02 '20 at 14:45
  • @MickaelB. the output is "1 2 3 4" – shubbbik May 02 '20 at 14:48
  • 1
    There is no `int**` anywhere in your code! The 2-D array `arr2d` decays to a *single* pointer to its first element, and its elements are stored in a contiguous block of memory. – Adrian Mole May 02 '20 at 14:48
  • 1
    And for this kind of declaration using `int[2][2]`, in the memory it is not stored as multidimentional array, but the elements are stored "inline". So if you increment after the first "row", you'll reach the second "row" – Mickael B. May 02 '20 at 14:48
  • @wildplasser in the course i take they showed this casting, and it's actually a part of a bigger question. it doesn't work when the 2 dimensional array is composed out of pointers, and i have to understand why. – shubbbik May 02 '20 at 14:49
  • 1
    `int *p; p = &arr2d[0][0];` would do the same, without the need for a cast, and without the `&array` decaying to a pointer to its first element. (which is an int, not a pointer) – wildplasser May 02 '20 at 15:02

3 Answers3

1

Why can I cast from int** (as a 2 dimension array) to int*?

You have a misunderstanding. Arrays are not pointers. They do decay to pointers in most contexts, but that's a question of evaluation, not nature. Accordingly, 2D arrays are arrays of arrays, not arrays of pointers. Thus, they do not decay to pointers to pointers, but rather pointers to arrays. There is no int** involved anywhere in your code.

Given this declaration:

int arr2d[2][2] = {{1, 2}, {3, 4}};

The relevant pointer assignment you can perform without a cast is

int (*arr2d_ptr)[2];

arr2d_ptr = arr2d;  // arr2d is not a pointer, but it does decay to one in this expression

arr2d_ptr is a pointer to a two-element array of int. The assignment makes it point to the first element of arr2d. If you convert that to type int *, then the result points to the first int in the array to which arr2d_ptr points. For example,

int *ip = (int *) arr2d_ptr;

That's natural, because that int is exactly the first part of the array. You can access it by index as ip[0] or *ip. And you can access the second int in that array as ip[1].

I guess the other facet of the question is about the expressions ip[2] and ip[3]. Arrays are contiguous sequences of elements. Arrays of arrays are not special in this regard: they are contiguous sequences of (smaller) arrays. Thus, the layout of your arr2d is like this:

array..|array..|

. If you overlay the layout of each of the member arrays then you get this:

int|int|int|int|

, which is exactly the same as the layout of a one-dimensional array of four int. This is why you can access all four ints by indexing ip (or arr in your example code).

Fun fact: because expressions of array type decay to pointers, you don't need a cast here. You could instead dereference arr2d_ptr to form an expression designating the first element of the 2D array, which is a 1D array, and let that decay to a pointer:

int *ip2 = *arr2d_ptr;

or, equivalently,

int *ip3 = *arr2d;
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

The values are stored in memory as if it was a linear array. That's why you can access them like that.

It doesn't work when the 2 dimensional array is composed out of pointers, and I have to understand why.

If you are working with an array of pointers you'll have to use a pointer to pointer, aka, double pointer.

It seems logic that if you want a pointer to access elements in an array of pointers you'll need a pointer to pointer.

//...
int *arr2d[2][2];
int **arr = (int**)arr2d;
//...

If you want to use it as a 2D array, which is what you would want in most cases, you can use:

Live sample

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

int main()
{
    int arr2d[2][2] = {{1, 2}, {3, 4}};

    int(*arr)[2] = arr2d; //pointer to 2d array with 2 columns

    for (int i = 0; i < 2; i++){
        for(int j = 0; j < 2; j++){
            printf("%d  ", arr[i][j]); //use it as if it were a 2d array
        }
        putchar('\n');
    }
}

A double pointer is not to be confused with a 2D array, these are different things, this thread has a nice explanation of what each of these mean.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
0

In memory, the array char a[4] = {1, 2, 3, 4} looks likes this:

[(1)(2)(3)(4)]

with () representing a byte of memory and [] hinting you where the array begins/ends.

The array char a[2][2] = {{1, 2}, {3, 4}} looks like this in memory:

[[(1)(2)][(3)(4)]]

See, there is no difference, as [] don't really exist in memory, they are just hints I used in my reply. Effectively, if you don't know anything about the arrays and just look at the content of the raw bytes in memory, you'll see:

(1)(2)(3)(4)

in both cases.

Thus instead of creating an array a[Xmax][Ymax] and accessing the elements using a[x][y], you can as well create the array as b[Xmax * Ymax] and access the elements using b[x * Xmax + y], as that's in fact what happens behind the scenes anyway.

And in C, you can always turn an array reference into a pointer, as an array reference is a reference to a memory location where an array is located and a pointer is a reference to a memory location (regardless if an array is located there or not). So

int a[5] = { ... };
int * b = a;

works as a is a reference to an int array, which are just several int values stored in memory, and b is a pointer to a memory location where an int value is stored. Well, at the address where the array a starts, an int value is stored, so this assignment is perfectly correct.

And m[3] just means "increase the memory address m references three times by the size of the value type m references and fetch the value from that address". And that works and returns the expected value, no matter if m is a pointer or an array reference. So while this syntax is actually intended for array access, it also works with pointers.

The syntax for a pointer would actually be *(m + 3), which means "increase the pointer address three times by the size of the value type it points to and then fetch the value from that address". Yet, just in the first case, this syntax would work with an array reference as well, since an array reference can always become a pointer when required.

Mecki
  • 125,244
  • 33
  • 244
  • 253