2

This Q in C, how come the address of array, &array, &array[0] are the same? made me investigate on pointers and arrays: I confirm they are very similar but not identical.

&a[0] is the verbose form for a or &a. But what about the precedence of & vs [] ?

When I put parens the wrong way like (&a)[1] this changed the value, but not (&a)[0].

#include <stdio.h>


int main(){
        int a[3] = {10,20,30};

        int *b;
        b = a;

        printf("%p a\n", a);
        printf("%p a+1\n", a+1);
        printf("%p a+2\n\n", a+2);

        printf("%p (&a)[0]\n",  (&a)[0]);
        printf("%p (&a)[1]\n",  (&a)[1]);
        printf("%p (&b)[0]\n",  (&b)[0]);
        printf("%p (&b)[1]\n\n",(&b)[1]);
        printf("%p cast\n\n", (void **)((&a) + 1)   );
        int *c = a;
        printf("%p\n",  c);
        printf("%p\n",  &c);
return 0;
}

output:

0x7ffe18053c9c a
0x7ffe18053ca0 a+1
0x7ffe18053ca4 a+2

0x7ffe18053c9c (&a)[0]
0x7ffe18053ca8 (&a)[1]
0x7ffe18053c9c (&b)[0]
(nil)          (&b)[1]

0x7ffe18053ca8 cast

0x7ffe18053c9c c  (int *c = a)
0x7ffe18053c90 &c       

I found (void **)((&a) + 1) to get the same result. Can't leave out a piece it seems. Suddenly &a does create a pointer, anonymously.

With index zero you get the base address (...c9c) both with a and its pointer b. But with index 1 strange things happen, and differently for b.

Is there a rule for this? I don't even know if I want to hear it! Is that what &a does besides being between a and &a[0]?


good A here by chux. His cast is simpler, but does it do the same?

difference between &array and &array[0] in C


From mentioned link I not only simplified my cast but now I think I know what it does and why the index or offset of 0 is special.

(void *)(&a + 0) is the cast. But why even cast? &a + 0 prints the base address. With offset + 1 the address advances a whole array size (not a pointer as I tried) - "one past the end". A second dimension. The base remains, but the offset changes.

For assignment:

int *c = a;
int (*d)[3] = &a;    // with some help from the left...
int *e = (int *)&a;  // ... or on the right side...
int *f = &a;         // or with a warning, but working

So (&a) generates a new type, but not a pointer? New reference to the same spot. Address of = pointer to = array of.

  • By following the [C operator precedence](https://en.cppreference.com/w/c/language/operator_precedence), you can pretty quickly ascertain what it will do: First we utilize the subscript, then the address-of operator (with no parenthesis present). The trick to the above code is that the address of the array is the same as the address of the first element (`a[0]`), so `&a[0] == &(*a)`. The other trick is that arrays "decay" into pointers. `a` is an array type, but `&a` (or `&any_variable`) will be the address of the variable, which is inherently a pointer. – Rogue May 04 '21 at 13:56

3 Answers3

3

TL;DR:

  • &a[0] returns the address of the first element of the array a. Since array in C is automatically decayed (converted) to the pointer to it's first element almost all the time, you will observe the same behavior by looking at a and &a[0]. But please be aware that there are cases when such conversion doesn't happen, the most obvious one would be using sizeof operator on the array.

  • (&a)[0] takes an address of a an array, and than treats the result as an array and tries to access it's first element. This is the same as just dereferencing resulting pointer, so (&a)[0] is the same as *(&a), which is the same as a. Than see above.

  • (&a)[1] is almost like previous, but it tries to access second element of the hypothetical array constructed from a pointer to array a. Since this is not actually an array, and there is no second element, this code is ill-formed, and triggers Undefined Behavior.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • "...treats the [array address] as an array...of itselves" one might add. You answer quite directly my Q. Remains the otherwise so symmetric pointer version with "b". But that comes down to an array of pointers as soon as "b" can take an offset. –  May 04 '21 at 16:10
1

First of all, if you are a beginner, I'd advise to simply not worry about these things for now, as they can get confusing and have limited practical use. You have be warned :) Somewhat advanced answer follows.


&a[0] is the verbose form for a or &a

Not really.

  • &a[0] is the address of the first element of the array.
  • a is the array, which in most cases "decays" into a pointer to the first element whenever used in an expression. So it may or may not be the same as &a[0] depending on context.
  • &a is the address of "the array itself". This is the same address as of the first element, but a different type. This & is one of them few cases of expressions where the array doesn't "decay". You get a pointer to array here. Given int a[n] then &a gives you the type int (*)[n]. Which is not compatible with the pointer to the first element, they are different types.

Now (&a)[0] changes the meaning of things. In case of &a[0] the [] has highest precedence so we de-reference the array element before & is applied. But with the parenthesis, we get a pointer to array first, then we dereference that one. Which works, because C allows pointer arithmetic on these pointer types too. The result is pointer arithmetic done just as if we had an array of arrays. Which we don't, so you'll just get really weird results from this, each [] jumping ahead one int[3] array in memory rather than one int.

You could use pointers to array like this:

int a[2][3] = { {10,20,30} };
int (*p)[2][3] = &a;
printf("%d %d %d", (*p)[0][0], (*p)[0][1], (*p)[0][2]);

Though that's just hard to read for nothing gained. A trick is to drop the left-most dimension and thereby skip one level of de-referencing:

int a[2][3] = { {10,20,30} };
int (*p)[3] = a;
printf("%d %d %d", p[0][0], p[0][1], p[0][2]);
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Good correction. "one of them few cases" "Which we doesn't": I really got it now, great explanations, thanks! –  May 04 '21 at 16:29
  • @cotentin Except that my grammar is horrible apparently, will fix :) – Lundin May 04 '21 at 17:51
  • Noo! Your style/grammar is perfect in that context! As I said in my Q: "Don't know if I even want to hear all the special rules". But I enjoyed your story. Still good now, after the fix :) –  May 04 '21 at 19:03
1

&a[0] is the verbose form for a or &a.

&a yields the same address value, but the type is different. This matters below.

But what about the precedence of & vs [] ?

Unary operators like & and * have lower precedence than postfix operators like [] and (), so &a[0] is parsed as &(a[0]) - you're taking the address of a[0].

When you write (&a)[0], you are indexing into the result of &a. The expression &a has type "pointer to 3-element array of int". The subscript operation a[i] is equivalent to the expression *(a + i) - given a starting address a, offset i elements (not bytes!) from that address and dereference the result1.

So the expression (&a)[0] is equivalent to *(&a + 0), which is equivalent to *&a, which is equivalent to a, which has type "3-element array of int". Unless this expression is the operand of the sizeof or unary & operators (as in &(&a)[0]), then it will "decay" to an expression of type "pointer to int" and the value of the expression will be the address of the first element of the array.

The expression (&a)[1] is equivalent to *(&a + 1). Like above, the type of this expression is "3-element array of int", and unless it is the operand of the sizeof or unary & operators, it "decays" to an expression of type "pointer to int" and the value of the expression will be the address of the first element of the array.

However...

Since &a has type "pointer to 3-element array of int", &a + 1 yields the address of the next 3-element array of int following a:

+----  +----+
|      | 10 | a[0]  <--- (&a)[0]
|      +----+
a      | 20 | a[1]
|      +----+
|      | 30 | a[2]
+----  +----+
|      | ?? |       <--- (&a)[1] 
|      +----+
??     | ?? |
|      +----+
|      | ?? |
+----  +----+

  1. Pointer arithmetic takes the size of the pointed-to type into account. If p points to an object of type T, then p + 1 points to the next object of type T following it, not the next byte. Array subscripting is defined in terms of pointer arithmetic - that's why array expressions "decay" to pointers in the first place.
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • (&a)[0][1] is really the same as a[1] (20) . Like saying "Imagine a high building...first floor, the others are not built yet". This should have given me a compile error in the first place! But thanks for dissecting this quite useless construct. –  May 04 '21 at 16:59
  • @cotentin: Why do you think should it have given you a compile error? Again, `(&a)[0]` resolves to `a`, so `(&a)[0][1] == a[1]`. It's ugly, it's non-intuitive, but it's valid. – John Bode May 04 '21 at 18:24
  • I mean my whole (&array)[1] idea. I wish this ugly, non-intuitive and useless construct were also invalid. Confused me too much. I was sure it would mean nothing when I tried. Then it seemed to mean sth. (I made also a pointer version...), and now I see it means sth else. A certain logical satisfaction, admitted, but zero usefulness. –  May 04 '21 at 19:22