Why is x[0] != x[0][0] != x[0][0][0]?

C++ArraysPointers

C++ Problem Overview


I'm studying a little of C++ and I'm fighting with pointers. I understand that I can have 3 level of pointers by declaring:

int *(*x)[5];

so that *x is a pointer to an array of 5 elements that are pointers to int. Also I know that x[0] = *(x+0);, x[1] = *(x+1)and so on....

So, given the above declaration, why is x[0] != x[0][0] != x[0][0][0] ?

C++ Solutions


Solution 1 - C++

x is a pointer to an array of 5 pointers to int.
x[0] is an array of 5 pointers to int.
x[0][0] is a pointer to an int.
x[0][0][0] is an int.

                       x[0]
   Pointer to array  +------+                                 x[0][0][0]         
x -----------------> |      |         Pointer to int           +-------+
               0x500 | 0x100| x[0][0]---------------->   0x100 |  10   |
x is a pointer to    |      |                                  +-------+
an array of 5        +------+                        
pointers to int      |      |         Pointer to int                             
               0x504 | 0x222| x[0][1]---------------->   0x222                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x508 | 0x001| x[0][2]---------------->   0x001                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x50C | 0x123| x[0][3]---------------->   0x123                    
                     |      |                                             
                     +------+                                             
                     |      |         Pointer to int                              
               0x510 | 0x000| x[0][4]---------------->   0x000                    
                     |      |                                             
                     +------+                                             

You can see that

  • x[0] is an array and will converted to pointer to its first element when used in an expression (with some exceptions). Therefore x[0] will give the address of its first element x[0][0] which is 0x500.
  • x[0][0] contains address of an int which is 0x100.
  • x[0][0][0] contains an int value of 10.

So, x[0] is equal to &x[0][0]and therefore, &x[0][0] != x[0][0].
Hence, x[0] != x[0][0] != x[0][0][0].

Solution 2 - C++

x[0] != x[0][0] != x[0][0][0]

is, according to your own post,

*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`  

which is simplified

*x != **x != ***x

Why should it be equal?
The first one is the address of some pointer.
The second one is the address of another pointer.
And the third one is some int value.

Solution 3 - C++

Here is the memory layout of your pointer:

   +------------------+
x: | address of array |
   +------------------+
            |
            V
            +-----------+-----------+-----------+-----------+-----------+
            | pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
            +-----------+-----------+-----------+-----------+-----------+
                  |
                  V
                  +--------------+
                  | some integer |
                  +--------------+

x[0] yields "address of array",
x[0][0] yields "pointer 0",
x[0][0][0] yields "some integer".

I believe, it should be obvious now, why they are all different.


The above is close enough for basic understanding, which is why I wrote it the way I wrote it. However, as haccks rightly points out, the first line is not 100% precise. So here come all the fine details:

From the definition of the C language, the value of x[0] is the whole array of integer pointers. However, arrays are something you can't really do anything with in C. You always manipulate either their address or their elements, never the entire array as a whole:

  1. You can pass x[0] to the sizeof operator. But that's not really a use of the value, its result depends of the type only.

  2. You can take its address which yields the value of x, i. e. "address of array" with the type int*(*)[5]. In other words: &x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. In all other contexts, the value of x[0] will decay into a pointer to the first element in the array. That is, a pointer with the value "address of array" and the type int**. The effect is the same as if you had casted x to a pointer of type int**.

Due to the array-pointer decay in case 3., all uses of x[0] ultimately result in a pointer that points the beginning of the pointer array; the call printf("%p", x[0]) will print the contents of the memory cells labeled as "address of array".

Solution 4 - C++

  • x[0] dereferences the outermost pointer (pointer to array of size 5 of pointer to int) and results in an array of size 5 of pointer to int;
  • x[0][0] dereferences the outermost pointer and indexes the array, resulting in a pointer to int;
  • x[0][0][0] dereferences everything, resulting in a concrete value.

By the way, if you ever feel confused by what these kind of declarations mean, use cdecl.

Solution 5 - C++

Let consider step by step expressions x[0], x[0][0] and x[0][0][0].

As x is defined the following way

int *(*x)[5];

then expression x[0] is an array of type int *[5]. Take into account that expression x[0] is equivalent to expression *x. That is dereferencing a pointer to an array we get the array itself. Let denote it like y that is we have a declaration

int * y[5];

Expression x[0][0] is equivalent to y[0] and has type int *. Let denote it like z that is we have a declaration

int *z;

expression x[0][0][0] is equivalent to expression y[0][0] that in turn is equivalent to expression z[0] and has type int.

So we have

x[0] has type int *[5]

x[0][0] has type int *

x[0][0][0] has type int

So they are objects of different types and by the way of different sizes.

Run for example

std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;

Solution 6 - C++

First thing I have to say that

> x [ 0 ] = * ( x + 0 ) = * x ;
> > x [ 0 ] [ 0 ] = * ( * ( x + 0 ) + 0 ) = * * x ; > > x [ 0 ] [ 0 ] [ 0 ] = * ( * ( * ( x + 0 ) + 0 ) ) = * * * x ; > > So * x ≠ * * x ≠ * * * x

From the following picture all things are clear.

  x[0][0][0]= 2000

  x[0][0]   = 1001

  x[0]      = 10

enter image description here

It is just an example, where value of x[0][0][0]=10

and address of x[0][0][0] is 1001

that address is stored in x[0][0]=1001

and address of x[0][0] is 2000

and that address is stored at x[0]=2000

So x[0][0][0] x[0][0] x[0]

.

EDITINGS

Program 1:

{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d   %d   %d   %d\n",x,*x,**x,***x);
printf("%d   %d   %d   %d   %d",x[0][0][0],x[0][0],x[0],x,&x);
}

Output

142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836

Program 2:

{
int x[1][1][1]={10};
printf("%d   %d   %d   %d \n ",x[0][0][0],x[0][0],x[0],&x);
}

Output

10   -1074058436   -1074058436   -1074058436 

Solution 7 - C++

If you were to view the arrays from a real-world perspective, it would appear as thus:

x[0] is a freight container full of crates.
x[0][0] is a single crate, full of shoeboxes, within the freight container.
x[0][0][0] is a single shoebox inside the crate, inside the freight container.

Even if it were the only shoebox in the only crate in the freight container, it is still a shoebox and not a freight container

Solution 8 - C++

There's a principle in C++ so that: a declaration of a variable indicates exactly the way of using the variable. Consider your declaration:

int *(*x)[5];

that can be rewritten as (for clearer):

int *((*x)[5]);

Due to the principle, we have:

*((*x)[i]) is treated as an int value (i = 0..4)
→ (*x)[i] is treated as an int* pointer (i = 0..4)
→ *x is treated as an int** pointer
→ x is treated as an int*** pointer

Therefore:

x[0] is an int** pointer
→ x[0][0] = (x[0]) [0] is an int* pointer
→ x[0][0][0] = (x[0][0]) [0] is an int value

So you can figure out the difference.

Solution 9 - C++

You are trying to compare different types by value

If you take the addresses you might get more of what you expect

Keep in mind that your declaration makes a difference

 int y [5][5][5];

would allow the comparisons you want, since y, y[0], y[0][0], y[0][0][0] would have different values and types but the same address

int **x[5];

does not occupy contiguous space.

x and x [0] have the same address, but x[0][0] and x[0][0][0] are each at different addresses

Solution 10 - C++

Being p a pointer: you're stacking dereferences with p[0][0], which is equivalent to *((*(p+0))+0).

In C reference (&) and dereference (*) notation:

p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])

Is equivalent to:

p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0

Look that, the &* can be refactored, just removing it:

p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)

Solution 11 - C++

The other answers are correct, but none of them emphasize the idea that it is possible for all three to contain the same value, and so they're in some way incomplete.

The reason this can't be understood from the other answers is that all the illustrations, while helpful and definitely reasonable under most circumstances, fail to cover the situation where the pointer x points to itself.

This is pretty easy to construct, but clearly a bit harder to understand. In the program below, we'll see how we can force all three values to be identical.

NOTE: The behavior in this program is undefined, but I'm posting it here purely as an interesting demonstration of something that pointers can do, but shouldn't.

#include <stdio.h>

int main () {
  int *(*x)[5];

  x = (int *(*)[5]) &x;

  printf("%p\n", x[0]);
  printf("%p\n", x[0][0]);
  printf("%p\n", x[0][0][0]);
}

This compiles without warnings in both C89 and C99, and the output is the following:

$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c

Interestingly enough, all three values are identical. But this shouldn't be a surprise! First, let's break down the program.

We declare x as a pointer to an array of 5 elements where each element is of type pointer to int. This declaration allocates 4 bytes on the runtime stack (or more depending on your implementation; on my machine pointers are 4 bytes), so x is referring to an actual memory location. In the C family of languages, the contents of x are just garbage, something left over from previous usage of the location, so x itself doesn't point anywhere—certainly not to allocated space.

So, naturally, we can take the address of the variable x and put it somewhere, so that's exactly what we do. But we'll go ahead and put it into x itself. Since &x has a different type than x, we need to do a cast so we don't get warnings.

The memory model would look something like this:

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

So the 4-byte block of memory at the address 0xbfd9198c contains the bit pattern corresponding to the hexadecimal value 0xbfd9198c. Simple enough.

Next, we print out the three values. The other answers explain what each expression refers to, so the relationship should be clear now.

We can see that the values are the same, but only in a very low level sense...their bit patterns are identical, but the type data associated with each expression means their interpreted values are different. For instance, if we printed out x[0][0][0] using the format string %d, we'd get a huge negative number, so the "values" are, in practice, different, but the bit pattern is the same.

This is actually really simple...in the diagrams, the arrows just point to the same memory address rather than to different ones. However, while we were able to force an expected result out of undefined behavior, it is just that—undefined. This isn't production code but simply a demonstration for the sake of completeness.

In a reasonable situation, you will use malloc to create the array of 5 int pointers, and again to create the ints that are pointed to in that array. malloc always returns a unique address (unless you're out of memory, in which case it returns NULL or 0), so you'll never have to worry about self-referential pointers like this.

Hopefully that's the complete answer you're looking for. You shouldn't expect x[0], x[0][0], and x[0][0][0] to be equal, but they could be if forced. If anything went over your head, let me know so I can clarify!

Solution 12 - C++

The type of int *(*x)[5] is int* (*)[5] i.e. a pointer to an array of 5 pointers to ints.

  • x is the address of the first array of 5 pointers to ints (an address with type int* (*)[5])
  • x[0] the address of the first array of 5 pointers to ints (same address with type int* [5])(offset address x by 0*sizeof(int* [5]) i.e. index*size-of-type-being-pointed-to and dereference)
  • x[0][0] is the first pointer to an int in the array (the same address with type int*) (offset address x by 0*sizeof(int* [5]) and dereference and then by 0*sizeof(int*) and dereference)
  • x[0][0][0] is the first int being pointed to by the pointer to an int (offset address x by 0*sizeof(int* [5]) and dereference and offset that address by 0*sizeof(int*) and dereference and offset that address by 0*sizeof(int) and dereference)

The type of int *(*y)[5][5][5] is int* (*)[5][5][5] i.e. a pointer to a 3d array of 5x5x5 pointers to ints

  • x is the address of the first 3d array of 5x5x5 pointers to ints with type int*(*)[5][5][5]
  • x[0] is the address of the first 3d array of 5x5x5 pointers to ints (offset address x by 0*sizeof(int* [5][5][5]) and dereference)
  • x[0][0] is the address of the first 2d array of 5x5 pointers to ints (offset address x by 0*sizeof(int* [5][5][5]) and dereference then offset that address by 0*sizeof(int* [5][5]))
  • x[0][0][0] is the address of the first array of 5 pointers to ints (offset address x by 0*sizeof(int* [5][5][5]) and dereference and offset that address by 0*sizeof(int* [5][5]) and offset that address by 0*sizeof(int* [5]))
  • x[0][0][0][0] is the first pointer to an int in the array (offset address x by 0*sizeof(int* [5][5][5]) and dereference and offset that address by 0*sizeof(int* [5][5]) and offset that address by 0*sizeof(int* [5]) and offset that address by 0*sizeof(int*) and dereference)
  • x[0][0][0][0][0] is the first int being pointed to by the pointer to an int (offset address x by 0*sizeof(int* [5][5][5]) and dereference and offset that address by 0*sizeof(int* [5][5]) and offset that address by 0*sizeof(int* [5]) and offset that address by 0*sizeof(int*) and dereference and offset that address by 0*sizeof(int) and dereference)

As for array decay:

void function (int* x[5][5][5]){
  printf("%p",&x[0][0][0][0]); //get the address of the first int pointed to by the 3d array
}

This is equivalent to passing int* x[][5][5] or int* (*x)[5][5] i.e. they all decay to the latter. This is why you wont get a compiler warning for using x[6][0][0] in the function but you will for x[0][6][0] because that size information is preserved

void function (int* (*x)[5][5][5]){
  printf("%p",&x[0][0][0][0][0]); //get the address of the first int pointed to by the 3d array
}
  • x[0] is the address of the first 3d array of 5x5x5 pointers to ints
  • x[0][0] is the address of the first 2d array of 5x5 pointers to ints
  • x[0][0][0] is the address of the first array of 5 pointers to ints
  • x[0][0][0][0] is the first pointer to an int in the array
  • x[0][0][0][0][0] is the first int being pointed to by the pointer to an int

In the last example, it is semantically much clearer to use *(*x)[0][0][0] rather than x[0][0][0][0][0], this is because the first and last [0] here are interpreted as a pointer dereference rather than an index into a multidimensional array, because of the type. They are however identical because (*x) == x[0] regardless of the semantics. You could also use *****x, which would look like you're dereferencing the pointer 5 times, but it is actually interpreted exactly the same: an offset, a dereference, a dereference, 2 offsets into an array and a dereference, purely because of the type you are applying the operation to.

Essentially when you [0] or * a * to a non array type, it is an offset and a dereference due to the order of precedence of *(a + 0).

When you [0] or * a * to an array type then it's an offset then an idempotent dereference (the dereference is resolved by the compiler to yield the same address – it's an idempotent operation).

When you [0] or * a type with a 1d array type then it's an offset then a dereference

If you [0] or ** a 2d array type then it's an offset only i.e. an offset and then an idempotent dereference.

If you [0][0][0] or *** a 3d array type then it's an offset + idempotent dereference then an offset + idempotent dereference then an offset + idempotent dereference then a dereference. The true dereference only occurs when the array type is fully stripped off.

For the example of int* (*x)[1][2][3] the type is unwrapped in order.

  • x has a type int* (*)[1][2][3]
  • *x has a type int* [1][2][3] (offset 0 + idempotent dereference)
  • **x has a type int* [2][3] (offset 0 + idempotent dereference)
  • ***x has a type int* [3] (offset 0 + idempotent dereference)
  • ****x has a type int* (offset 0 + dereference)
  • *****x has type int (offset 0 + dereference)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLeo91View Question on Stackoverflow
Solution 1 - C++haccksView Answer on Stackoverflow
Solution 2 - C++deviantfanView Answer on Stackoverflow
Solution 3 - C++cmaster - reinstate monicaView Answer on Stackoverflow
Solution 4 - C++d125qView Answer on Stackoverflow
Solution 5 - C++Vlad from MoscowView Answer on Stackoverflow
Solution 6 - C++apmView Answer on Stackoverflow
Solution 7 - C++David Optional CourtenayView Answer on Stackoverflow
Solution 8 - C++Nghia BuiView Answer on Stackoverflow
Solution 9 - C++Glenn TeitelbaumView Answer on Stackoverflow
Solution 10 - C++LucianoView Answer on Stackoverflow
Solution 11 - C++PuragView Answer on Stackoverflow
Solution 12 - C++Lewis KelseyView Answer on Stackoverflow