std124_lf
std124_lf

Reputation: 154

To what does a 2D array points to?

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!

Upvotes: 0

Views: 159

Answers (2)

John Bode
John Bode

Reputation: 123458

*(*(arr+8)) = 12;

translates to

arr[8][0] = 12;

which is outside of the array bounds. The behavior of writing outside array bounds is undefined - it may crash immediately, it may corrupt data in the stack, or it may work without any apparent issues.

Remember that unless it is the operand of the sizeof or unary & operators, an expression of type "N-element array of T" is converted, or "decays", to an expression of type "pointer to T" and the value of the expression is the address of the first element of the array. The expression arr has type "6-element array of 8-element array of int; under most circumstances, it "decays" to type "pointer to 8-element array of int", or int (*)[8].

Adding 1 to a pointer value yields a pointer to the next object of the pointed-to type. If the expression arr yields a pointer to an 8-element array of int, then the expression arr + 1 yields a pointer to to the next 8-element array of int immediately following. Thus

      int          int *        int (*)[8]
+–––+ 
|   | arr[0][0]    p            arr
+–––+      
|   | arr[0][1]    p+1 
+–––+
|   | arr[0][2]    p+2
+–—–+
 ...
+–—–+
|   | arr[0][6]    p+6
+–––+
|   | arr[0][7]    p+7
+–––+
|   | arr[1][0]    p+8          arr+1
+—––+

This is why *(*(arr + 8)) == arr[8][0] - arr + 8 yields a pointer to the 8’th 8-element array of int following arr. This is why *(*(arr + 1)) == arr[1][0].

Upvotes: 0

Eric Postpischil
Eric Postpischil

Reputation: 222650

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.

Upvotes: 3

Related Questions