Home > Software engineering >  To what does a 2D array points to?
To what does a 2D array points to?

Time:10-10

That's might be a dumb question, but I was reading a book and they splattered to me this code

int arr[6][8];
int *ptr = &arr[0][0];
*(ptr 8) = 12; // arr[1][0]
printf("%d\n", arr[1][0]);

Now... I get why this pointer points to that cell, but if I had to use the arr identifier I should have done it in this way instead

*(*(arr 1)) = 12; // arr[1][0]

But I don't get why I can't say in this other way instead

*(*(arr 8)) = 12;

Maybe my knowledge of pointers and two-dimension arrays is that poor, but I'd really like to understand how these kinds of things work.

Thanks in advance for the answers!

CodePudding user response:

An array, whether one-dimensional, two-dimensional, or more, does not point to anything. It is a set of objects of the same type in contiguous memory.

After int arr[6][8];, arr is an array of 6 arrays of 8 int. If each int uses four bytes, then the entire array uses 6•8•4 = 192 bytes in memory, and sizeof arr will evaluate to 192, because arr is the array, and sizeof gives the size of its operand.

When you use arr in an expression, it will be automatically converted to a pointer to its first element, except when it is the operand of sizeof or of unary &.1 Because arr is an array of arrays, its first element is also an array. The first element of arr is arr[0], which is an array of 8 int. So, in arr 1, arr is converted to a pointer to its first element. That pointer is &arr[0], the address of arr[0].

When an integer, say n is added to a pointer, the result points to n further elements long in the array. So, when we add 1 to &arr[0], we get &arr[1]. Thus, in arr 1, arr is automatically converted to &arr[0], producing &arr[0] 1, and then the addition produces &arr[1].

Thus, arr 1 is &arr[1], which is also the place where arr[1][0] is. (Note that while &arr[1] and &arr[1][0] point to the same place in memory, they have different types, so the compiler treats them differently when doing arithmetic with them.)

After int *ptr = &arr[0][0];, ptr of course points to arr[0][0]. Then ptr 8 points to where arr[0][8] would be if there were such an element. Of course, there is no, since arr[0] has only elements from arr[0][0] to arr[0][7]. ptr 8 points one beyond arr[0][7].

That pointer arithmetic is defined by the C standard; you are allowed to point “one beyond the last element.” However, that is only a placeholder pointer, useful for arithmetic and comparisons. The C standard does not happen when you dereference the pointer, with *(ptr 8).

We know that arr[1][0] is in the place where arr[0][8] hypothetically would be. However, the C standard does not give us rules that say we can definitely use *(ptr 8) to access arr[1][0]. So that code does not have behavior defined by the C standard. Many compilers will treat it has accessing arr[1][0], though.

As you note, *(*(arr 1)) can be used to access arr[1][0]. The way this works is:

  • arr is automatically converted to &arr[0].
  • Adding 1 gives &arr[1].
  • * dereferences &arr[1], producing arr[1].
  • arr[1] is an array, so it is automatically converted to a pointer to its first element. This produces &arr[1][0].
  • * dereferences &arr[1][0], producing arr[1][0].

In contrast, *(*(arr 8)) does not work to access arr[1][0]:

  • arr is automatically converted to &arr[0].
  • Adding 8 gives &arr[8].

Now we are well beyond where arr ends. arr has only elements from arr[0] to arr[5], so arr[8] is beyond the end.

Note that adding 8 to &arr[0] moved the pointer by 8 elements of the arr array, not by 8 eleemnts of the arr[0] array. That is, it moved it by 8 arrays of 8 int, not by 8 int. That is because the type of &arr[0] is “pointer to array of 8 int”, not “pointer to int”.

When you add 8 to a “pointer to int”, you move it by 8 int. When you add 8 to a “pointer to an array of 8 int”, you move it by 8 arrays of 8 int.

Footnote

1 All arrays are automatically converted like this, and there is one more exception for string literals. When a string literal, which is an array of characters, is used to initialize a character array, it is not automatically converted to a pointer.

CodePudding user response:

If you only define the pointer and then assign its value, things may be more clear:

int array[2][3] = {0, 1, 2, 3, 4, 5}; // array declared & initialized
int *ptr; // pointer is declared but not initialized (is not assigned a value)
// above, pointer "ptr" points to some random memory address

ptr = &array[0][0]; // pointer is assigned to point to the starting element of the array
ptr = array; // same as above; pointer is assigned to point to the starting element of the array

// "ptr" is the address of the memory the pointer points to
// "*ptr" is the integer value stored in the memory address the pointer points to
// if the array is declared as "array[m][n]", then;
// "*(ptr   (i * n)   j)" is equivalent to "array[i][j]" i.e. the value
// "(ptr   (i * n)   j)" is equivalent to "&array[i][j]" i.e. the memory address

@John Bollinger has a point! In fact he is right. Array names in C is not pointers as we expect. Code below explains:

// array declared below on stack
int a[3] = {1, 2, 3};

// same array with same values stored on heap
int *b = malloc(3 * sizeof(int));
*(b   0) = 1;
*(b   1) = 2;
*(b   2) = 3;

printf("starting address of array a is %p\n", a);
printf("when treated like a pointer    %p\n", &a);
printf("see, they are same!!!\n");
printf("\n");
printf("starting address of array b is %p\n", b);
printf("memory address of the pointer  %p\n", &b);
printf("see, they are different!!!\n");
  • Related