149

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] ?

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Leo91
  • 1,741
  • 3
  • 13
  • 20
  • ...isn't that the same as `int** x[5]`? ie, the parentheses don't change anything, right? – celticminstrel Jul 05 '15 at 14:32
  • 58
    `x[0]` , `x[0][0]` and `x[0][0][0]` have different types. They cannot be compared. What do you mean by `!=` ? – bolov Jul 05 '15 at 14:32
  • 4
    @celticminstrel they are not the same: `int **x[5]` is an array of 5 elements. An element is a pointer to pointer to int` – bolov Jul 05 '15 at 14:33
  • @bolov I mean that theis addresses are different, even if x[0] ,x[0][0] ,x[0][0][0] is equivalent to *(x+0), *(x+0+0), *(x+0+0+0) – Leo91 Jul 05 '15 at 14:34
  • 5
    @celticminstrel `int** x[5]` would be an array of five pointers that point to pointers that point to int. `int *(*x)[5]` is a pointer to an array of five pointers that point to int. – Emil Laine Jul 05 '15 at 14:34
  • @then write address: `&x[0]` or : `address of ...` – bolov Jul 05 '15 at 14:35
  • 1
    Ahhh, right, okay. Pointer syntax can get confusing. >_> – celticminstrel Jul 05 '15 at 14:36
  • 5
    @celticminstrel [right-left rule](http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html) , [spirale rule](http://c-faq.com/decl/spiral.anderson.html) , [C gibberish ↔ English](http://cdecl.org/) and your' on your way to becoming a [three star programmer](http://c2.com/cgi/wiki?ThreeStarProgrammer) :) – bolov Jul 05 '15 at 14:38
  • Well, I do understand the syntax for such arcane structures as pointer-to-function (even one returning a pointer-to-function... I think... haven't done that recently, thanks to `std::function`, and perhaps never will again) or reference-to-array, but this usage I guess caught me off guard. – celticminstrel Jul 05 '15 at 14:40
  • 2
    If you have to fight with pointers the definition is too complicated. `typedef int *ptr; typedef ptr arr[5]; arr *x;` – Pete Becker Jul 05 '15 at 16:41
  • 1
    `x[0] != x[0][0]` is not true: it's nonsensical. –  Jul 05 '15 at 17:06
  • 2
    This question is hardly practical and is silly with a plain answer to this: each pointer to another pointer holds its own address. Simple. – Poriferous Jul 05 '15 at 19:18
  • 5
    @Leo91: Firstly, you have *two* levels of pointers here, not three. Secondly, what does `x[0] != x[0][0] != x[0][0][0]` mean? This is not a valid comparison in C++. Even if you split it up into `x[0] != x[0][0]` and `x[0][0] != x[0][0][0]` it is still not valid. So, what does your question mean? – AnT stands with Russia Jul 06 '15 at 00:11
  • @rakeb.void Cause the question is stupid. If nothing, that line would cause a compilation error. – BЈовић Jul 06 '15 at 10:35
  • I just want to mention `int x[5]` is not a vector, it is an array. In C++ there is a big difference between them! – henje Jul 06 '15 at 14:04
  • @henje you're absolutely right. – Leo91 Jul 06 '15 at 15:40
  • 3
    @PeteBecker hiding the complexity behind an array typedef arguably makes things even harder to understand. Array typedefs behave unintuitively – M.M Jul 07 '15 at 05:26
  • 2
    @MattMcNabb - this is engineering, not intuition. If you're guessing about how arrays work then you shouldn't be using them. – Pete Becker Jul 07 '15 at 11:59
  • 1
    @PeteBecker for example, `void f(foo x) { ...do stuff with x...)` looks like it will not modify anything in the caller code; but if `foo` is an array typedef then this is an unpleasant surprise. Also, `sizeof x` will misbehave here. – M.M Jul 07 '15 at 12:24
  • 1
    Pointers and arrays are confusing and `typedef` creates more confusion in this case. Better to avoid `typedef` here. – haccks Jul 07 '15 at 12:29
  • 1
    @MattMcNabb That's an argument that arrays are dangerous in general. An array typedef still helps here nevertheless. – Potatoswatter Jul 07 '15 at 23:02
  • It's just such a weird question... Who ever told you (or where did you ever read) that `x[0][0][0]` is equal to `*x(0+0+0)`? If a language has such horrible semantics it would just be plainly unusable. Consider `x[2][3][1]` vs `x[1][3][2]`. Should they be expected to be the same in any reasonable language? – xji Jul 08 '15 at 05:52
  • 1
    I don't have the rep to vote, but seconded. – dbush Jul 08 '15 at 17:54

12 Answers12

261

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].

haccks
  • 104,019
  • 25
  • 176
  • 264
  • This diagram is a bit confusing to me: `0x100` should appear immediately to the left of the box containing `10`, the same way that `0x500` appears to the left of its box. Instead of it being far to the left, and below. – M.M Jul 07 '15 at 05:29
  • @MattMcNabb; I do not think it should be confusing, but changes as per your suggestion for more clarity. – haccks Jul 07 '15 at 09:14
  • 4
    @haccks - My pleasure :) The reason why that diagram is great is because you don't even need the explanation that you gave that follows it. That diagram itself is self-explanatory that it already answers the question. The text that follows is simply a bonus. – rayryeng Jul 07 '15 at 20:48
  • 1
    You can also use yed, a diagramming software. It helps me a lot for organizing my thoughts – rpax Jul 09 '15 at 16:07
  • @GrijeshChauhan I use asciiflow for the comments ofthe code, yeD for presentations :) – rpax Feb 01 '16 at 18:09
  • @GrijeshChauhan; Hi. How Can I help you? – haccks Aug 22 '19 at 08:58
133
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.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
deviantfan
  • 11,268
  • 3
  • 32
  • 49
  • I can't understand... if x[0] ,x[0][0] ,x[0][0][0] is equivalent to *(x+0), *(x+0+0), *(x+0+0+0), why they should have different addresses? – Leo91 Jul 05 '15 at 14:36
  • 41
    @Leo91 `x[0][0]` is `(x[0])[0]`, i.e. `*((*(x+0))+0)`, not `*(x+0+0)`. Dereference happens before the second `[0]`. – Emil Laine Jul 05 '15 at 14:39
  • 4
    @Leo91 `x[0][0] != *(x+0+0)` just like `x[2][3] != x[3][2]`. – ozg Jul 06 '15 at 13:55
  • @Leo91 The second comment that you "got it now" was removed. Do you not understand something (which could be explained better in the answer), or is this not your doing? (some people like to delete comments without much informative content) – deviantfan Jul 06 '15 at 17:34
  • @deviantfan sorry I can't understand what you mean. I understand the answers as well many comments that helped meto clarify the concept. – Leo91 Jul 07 '15 at 10:28
50

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".

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 1
    `x[0]` is not the address of array. – haccks Jul 06 '15 at 09:58
  • 1
    @haccks Yes, following the letter of the standard, `x[0]` is not the address of the array, it is the array itself. I have added an in-depth explanation of this, and of why I wrote that `x[0]` is the "address of array". I hope you like it. – cmaster - reinstate monica Jul 06 '15 at 21:03
  • awesome graphs that explain it perfectly well! – M.K Apr 12 '19 at 10:47
  • "However, arrays are something you can't really do anything with in C." --> Counter example: `printf("%zu\n", sizeof x[0]);` reports the size of the array, not the size of a pointer. – chux - Reinstate Monica Jun 08 '20 at 20:04
  • @chux-ReinstateMonica And I went on to say "You always *manipulate* either their address or their elements, never the entire array as a whole", followed by point 1 of the enumeration where I talk about the effect of `sizeof x[0]`... – cmaster - reinstate monica Jun 08 '20 at 22:08
18
  • 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.

haccks
  • 104,019
  • 25
  • 176
  • 264
d125q
  • 1,666
  • 12
  • 18
11

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;
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
10

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 
apm
  • 525
  • 6
  • 19
  • 3
    Your answer is misleading. `x[0]` does not contain ant address. Its an array. It will decay to pointer to its first element. – haccks Jul 06 '15 at 10:27
  • Ummm... what does it mean? Your edit is like cherry on cake to your wrong answer. It makes no sense – haccks Jul 06 '15 at 14:28
  • @haccks If it is using pointers alone, this answer will be right. There will be some changes in address section when using Array – apm Nov 21 '17 at 10:16
7

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

haccks
  • 104,019
  • 25
  • 176
  • 264
  • 1
    wouldn't `x[0][0]` be a single crate full of pieces of paper that have the locations of shoeboxes written on them? – wchargin Jul 09 '15 at 01:16
4

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.

Nghia Bui
  • 3,694
  • 14
  • 21
  • 1
    `x[0]` is an array of 5 ints, not a pointer. (it may decay to a pointer in most contexts but the distinction is important here). – M.M Jul 07 '15 at 05:31
  • Okay, but you should say: x[0] is an array of 5 pointers int* – Nghia Bui Jul 07 '15 at 06:27
  • To give the correct derivation per @MattMcNabb: `*(*x)[5]` is an `int`, so `(*x)[5]` is an `int *`, so `*x` is an `(int *)[5]`, so `x` is an `*((int *)[5])`. That is, `x` is a pointer to a 5-array of pointers to `int`. – wchargin Jul 09 '15 at 01:16
2

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

Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
2

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)
Luciano
  • 2,695
  • 6
  • 38
  • 53
  • What are you trying to show with everything after your first sentence? You just have lots of variations on `p == p` . `&(&p[0])[0]` is different to `p[0][0]` – M.M Jul 20 '15 at 12:24
  • The guy asked why 'x[0] != x[0][0] != x[0][0][0]' when x is a pointer, right ? I tried to show him, that he can be trapped by the C notation of dereference (*) when he stacked [0]'s. So, it's a try to show him the correct notation to have x be equal x[0], referencing x[0] again with &, and so on. – Luciano Jul 20 '15 at 12:44
1

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!

Purag
  • 16,941
  • 4
  • 54
  • 75
  • I would say that another weird use of pointers I have seen ever. – haccks Jul 11 '15 at 10:47
  • @haccks Yeah, it's pretty weird, but when you break it down, it's just as basic as the other examples. It just happens to be a case where the bit patterns are all the same. – Purag Jul 11 '15 at 22:05
  • Your code causes undefined behaviour . `x[0]` doesn't actually represent a valid object of the correct type – M.M Jul 20 '15 at 11:46
  • @MattMcNabb it IS undefined, and I'm very clear about that actually. I disagree regarding the type. `x` is a pointer to an array, so we can use the `[]` operator to specify an offset from that pointer and dereferencing it. What's strange there? The result of `x[0]` is an array, and C doesn't complain if you print that using `%p` since that's how it's implemented underneath anyway. – Purag Jul 20 '15 at 11:58
  • And compiling this with the `-pedantic` flag produces no warnings, so C is fine with the types... – Purag Jul 20 '15 at 12:00
  • @Purag no warnings doesn't mean the code is correct. If you did not cast you get an error. Your cast just makes the compiler hide the message, it doesn't fix the code – M.M Jul 20 '15 at 12:18
  • `x[0]` only works if the memory location being pointed to by `x` contains an object of the type which `x` is a pointer to. In your code it doesn't. It's the same error as `int j; float *x = (float *)&j; foo(x[0]);`. That's the rules of the C language. People who learned by C trial and error might have overlooked this rule. – M.M Jul 20 '15 at 12:22
  • Ah, I see your point @MattMcNabb. You're absolutely right but I think I'm clear in saying this is undefined behavior and is simply to address the idea that they can't be the same. While the types are pretty screwy here I think it's an interesting demonstration. Should I add a bigger disclaimer? – Purag Jul 20 '15 at 12:27
  • I don't see where you say it is undefined behaviour. You mention that `x[0]` it might contain garbage in one sentence, but the rest of your answer speaks as if `x[0]` has definite content. For example "but they absolutely could be." - in fact they absolutely could not be. You could *appear* to get results that look like those values are the same, only if you write a program that has undefined behaviour. Which isn't really saying anything, since you can produce *any* result from undefined behaviour. In C a pointer cannot point to itself and be dereferenced. – M.M Jul 20 '15 at 12:35
  • @MattMcNabb in the third to last paragraph, I say it is "entirely undefined." Plus, `x` has definite content when we assign a value to it. But, if you believe this answer will cause further confusion, I can delete it and perhaps come back to it in a while. :) – Purag Jul 20 '15 at 12:38
  • "it works" and "it is entirely undefined" are incompatible. Your post is interesting but I think it would be improved by noting right up where you first do the cast that everything that follows is not covered by the C standard. – M.M Jul 20 '15 at 12:47
  • @MattMcNabb I edited the answer, let me know if I can do more to improve it! :) – Purag Jul 20 '15 at 12:57
0

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)
Lewis Kelsey
  • 4,129
  • 1
  • 32
  • 42