Reputation: 154
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
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
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]
.&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]
.&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
.
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