Pointers and array

Hello, I read from a book exercise for a challenge. How to print out each letter of char array a[][][] by two different pointers pa and ppa in the example?
I have tried my code for letter "r" by testing without full understanding as only the first one worked.

#include<stdio.h>
int main()
{
    char a[4][3][2] = { {{'a', 'b'}, {'c', 'd',}, {'e', 'f'}},
                                   {{'g', 'h'}, {'i', 'j'}, {'k', 'l'}},
                                   {{'m', 'n'}, {'o', 'p'}, {'q', 'r'}},
                                   {{'s', 't'}, {'u', 'v'}, {'w', 'x'}}
                                 };

    char (*pa)[2] = &a[1][0];
    char (*ppa)[3][2] = &a[1];

//I tried out this one without much understanding.
    printf("The letter r can be reached by pointer *pa is: %c\n", pa[4][3]);
//No error at compiling time, but did not print out the letter r.
    printf("The letter r can be reached by pointer *ppa is: %c\n", ppa[4]);

    return 0;
}

Thank you in advance!

This is a highly unusual way of using arrays and pointers.

Seeing where *pa starts may help:

printf("The first element of *pa is: %c\n", pa[0][0]);

The [1][0] has moved you one down the outermost dimension of the array, so it starts at {{'g', 'h'}, {'i', 'j'}, {'k', 'l'}}

[1][1] would move you one down and one in, so you'd start at {{'i', 'j'}, {'k', 'l'}}

1 Like

Thanks Coron!

I know the author is to emphasize the relationship of pointer and array address. But I could not figure out the way of this complicate case. Could you please explain a little about these two lines, especially a[][] and a[]?

    char (*pa)[2] = &a[1][0];
    char (*ppa)[3][2] = &a[1];

And I have tried out:

printf("The first element of *pa is: %c\n", pa[0][0]); // g
printf("The first element of *pa is: %c\n", pa[1][0]); // h
printf("The first element of *pa is: %c\n", pa[0][1]); // i
printf("The first element of *pa is: %c\n", pa[1][1]); // j

How to get a ~ f ? and pa[0][2], p[1][0], ppa[0][0][2] all get letter "i" ???? Need more reading! Thank you again. Have a nice weekend.

---------- Post updated 09-14-13 at 01:56 AM ---------- Previous update was 09-13-13 at 06:02 PM ----------

Kind of get it now!
Because of the definition of (*pa)[2]=&a[1][0] and (*ppa)=&a[1], there is no way to get letter "a" ~ "f" unless I define:

    char (*pa)[2] = &a[0][0];
    char (*ppa)[3][2] = &a[0];

Wait a minute!!!.....Can anyone explain why there are multiple addresses to get the letter "r" ? I did an exhaust search, found out

pa[0][11], pa[1][9], pa[2][7], pa[3][5], pa[4][3], pa[5][1] 

all give "r"; and also

ppa[0][0][11], ppa[0][1][9], ppa[0][2][7], ppa[0][3][5], ppa[0][4][3], ppa[0][5][1], ppa[1][0][5], ppa[1][1][3], ppa[1][2][1] 

all give letter "r".
This must be something with pointer and array, and I have noticed the first 6 ppa[][][] addresses have the same index by ignoring the first dimension [0][][] . Two points I need experties:
1) multiple addresses to the same letter;
2) some index (11, 9, 7, 5) are way bigger than the max array dimension (which is 4, intuitively.).
Interesting, but something very new to me. Thanks a lot!

It has to do with how memory is organized.

Your big array is one block of memory organized linearly, like

'a', 'b', 'c', 'd','e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x'

...and the only difference is the equation the compiler generates to access it.

If it's an n*2 array, the compiler does something like arr[(2*index2)+index1] to access the array. If you really wanted, you could ignore index2, and just step through the entire array with index1, but if you don't know what you're doing you might step beyond the bounds of the array by accident.

1 Like

Thanks Corona!
I am getting close to the point now, except the two addresses:

ppa[1][1][3],  ppa[1][2][1]

which seem not out of the boundaries in the three dimension 4, 3 & 2.

What is the question?

My question is why both ppa[1][1][3], ppa[1][2][1] print out the same letter "r"?
ppa[1][2][1] is obvious, but ppa[1][1][3] seems to me not rational. Is it because [3] is out of the bounds again, which is supposed to be 2 as defined as ppa[3][2] ? I am a little bit confused C automatic changes the address behind, somehow.
Thanks!

What bounds? Remember it's a big 1D array. Nothing stops you from going beyond the 'size'.

Also remember, 3 is TWO away from two. 0 isn't, 1 isn't, 2 is outside, 3 is outside.

No, I did not get you.
As per the question is to use (*pa)[1] = &a[1][0]; (*ppa)[3][2] = &a[1] to reach "r", what is the correct answer although multiple addresses can print it out? Need more digestion. Can you suggest a good reference book about this? Thanks a lot!

As Corona688 already said multidimensional arrays are nothing but a logical aggregation of 1D arrays. The data items are laid out sequentially so once you get that down it should be easy to visualize how they are combined logically to form 2 or 3 or n dimensional arrays. And the book I really like is "Expert C Programming" which has an entire chapter devoted to explaining multidimensional arrays along with lots of examples and pictures.

1 Like

Thanks Shamrock!

I was trying to catch the "dynamic" of the pointer in this example. The part that tricks me is (*pa)[1] = &a[1][0]; (*ppa)[3][2] = &a[1] .
I want to confirm the structure of (*pa)[1] and (*ppa)[3][2] , which confused me with the assginment. &a[1][0] is the address for letter 'g',Is my understanding right? but what is &a[1] ? I feel getting close to the point, but just not quite sure without a guide/reference/expert.
I read from the book saying the last dimension must always be given. I am not sure here for a[j][k], &a[1][0], i=1, j=0, k not assigned; and &a[1], i = 1, j, k not assigned. Or, (*pa)[1] is for k = 1; i, j not assigned here. Got totally lost. I wish the author had given explanation for this question.

Thanks!

Try printing its contents and see.

1 Like

Thanks Corona688!
I fully understand the full array is actually a big 1-dimension array from your previous reply.
I tried:

printf("The value in a[1] is: %p\n", a[1]);
    printf("The value in a[0] is: %p\n", a[0]);
    printf("The value in a[1][0] is: %p\n", a[1][0]);
    printf("The value in a[1][1] is: %p\n", a[1][1]);


Output:
The value in a[1] is: 0x7fff80c346a6
The value in a[0] is: 0x7fff80c346a0
The value in a[1][0] is: 0x7fff80c346a6
The value in a[1][1] is: 0x7fff80c346a8

The value in a[1][0][0] is: g
The value in a[1][0][1] is: h

first 4 printf() print addresses in fact, and I seem understand that:
Because a[1] is the first address of a two-way (3x2) array, so a[1] is the same as a[1][0], which in turn is the first address of two-elements array, so that (now value, not address) a[1][0][0] = g; a[1][0][1] = h.
My confusion is: How is the connection with the pointer (*pa)[2]? Here the [2] is the last dimension of the array a[4][3][2]. I treat each "2-element array" as a box---a single unit. so that (*pa)[2] points a[4][3], each unit is an address of an array. Right?
I thought pa[4][3] should point to an address of the array{'q', 'r'}, but it actually points to the element, char "r". I lost the connection here (*pa)[2] vs. pa[4][3].
Another confusion is the declaration of (*pa)[2] = &a[1][0] and (*ppa)[3][2] = &a[1] . The author is to emphasize the address of array and pointer. By array address only one anwer for char "r" which is a[2][2][1]. With pointer, you can have many ways(!?) What is the trick to connect two of them, say, if I declare (*pa)[2] = &a[2][0] ?

Sorry I am so slow with this important point.

Going back to your first post you have...

char a[4][3][2] = {...};    /* a 3-dimensional array */
char (*pa)[2] = &a[1][0];   /* pa is a pointer to array of 2 chars */
char (*ppa)[3][2] = &a[1];  /* ppa is a pointer to a 2-dimensional array of chars */

So a[4][3][2] is made up of 4 items...each item is an array of 3 arrays...each of those 3 arrays is an array of 2 chars.
From this you can see that the element a[1] is an array of 3 arrays each made up of an array of 2 chars
Effectively saying that a[1] is a 2-d array of rank 3 x 2 and &a[1] is a pointer to that aka (*ppa)[3][2].

&a[1][0] is a pointer to array of 2 chars...so it is a pointer to the array that contains 'g' and 'h'.

As a[1] is an array of 3 arrays each made up of an array of 2 chars...&a[1] is a pointer to that aka (*ppa)[3][2]

Omg! which book have you been reading
Read "last dimension must always be given" to say that only the innermost dimension can be omitted while the rest need to be given so that the compiler can allocate storage for the array.
Moreover this is only true when initializing the array...if the array is defined without initialization all dimensions must be given...

1 Like

Thanks Shamrock!
Good hints are

So, a[1] is: {{'g', 'h'}, {'i', 'j'}, {'k', 'l'}}
......
Read more last night, and finally I got the answers, just for somebody who hits the same wall as I did.

as (*pa)[2] = &a[1][0], which means *pa is a "2-col" pointer start from {'g', 'h'} in the array a ; so that char 'r' is in {'q', 'r'} , the 6th member of pa ---> pa[5], which in turn is a "2-char" array, and with the second index = 1. So the answer is pa[5][1] ;
as (*ppa)[3][2] = &a[1] , which defines *ppa is a "3(row) x 2(col) array " pointer start from {{'g', 'h'}, {'i', 'j'}, {'k', 'l'}} in the array a . For char 'r' is on the 2nd row, 3rd col, within the 3rd col it is in the 2nd col. So the answer is ppa[1][2][1].

The reason why there is multiple answer to get the 'r' is because C actually only has "1-dimension" or linear arrangement of the array members in memory as Corona688 pointed out. When you split the linear array in different combinations, counted by 1, by 2, or by 3, 4, 5.... Actually pa[11] prints 'r', pa[3][2], pa[4][3] pa[5][1]...all point to 'r', but only pa[5][1] is appropriate to the original question. Similar for ppa, but only pa[1][2][1] is the correct answer as it was defined as a 3x2 array.

Shamrock and Corona688, please correct me if I interpreted anything wrong.
Thanks a lot again!

---------- Post updated 09-25-13 at 12:50 PM ---------- Previous update was 09-24-13 at 05:50 PM ----------

To confirm my digestion I tried another two situations by change the pa and ppa definitions:

the answers to get char 'r' are

pa[7][1]; 
ppa[-1][2][1]

Here it is very interesting the index can be negative. Amazing C!

That's just how the processor, and two's-complement math works, not a special property of C per-se. It lets you do so, when most languages would protect you from it, but C, which hardcodes so much into assembly language at compile-time (same reason sizeof() can't be used dynamically) is very limited in what it can warn you about.

In most situations, a negative array index would edge into incorrect or even invalid memory, causing strange behavior or crashes. It's only okay here since you know you're not at the beginning of the data of interest.

So, use with care.

Sure! It was only to confirm my understanding on the pointer to multi-dimension array. Thanks a lot!

a[1] is not the first address of the 2-d array...it is the name of the 2-d array of rank 3x2...and since the name of an array is a synonym for the location of its initial element hence it contains the address of the second 2-d array of rank 3x2.

a[0] is this array -> {{'a', 'b'}, {'c', 'd'}, {'e', 'f'}}
a[1] is this array -> {{'g', 'h'}, {'i', 'j'}, {'k', 'l'}}

Yes your understanding is correct...

I'd suggest that you do those pointer arithmetic calculation using a pencil and paper...

char (*pa)[2] = &a[1][0];  /* original definition of pa */
pa points to -> {'g', 'h'}
pa[4] = *(pa + 4)  /* an array-and-index expression is equivalent to one written as a pointer and offset */
(pa + 4) points to -> {'o', 'p'}, and *(pa + 4) or pa[4] is that array via dereferencing

Since arrays are laid out sequentially...char 'r' is the 3rd element of the array "pa[4]" hence pa[4][3] == 'r'

If (*pa)[2] = &a[2][0] then you can get to the letter 'r' by... pa[0][5] or pa[2][1] or pa[1][3] or pa[3][-1]

Hope this helps...or have I confused you even more :smiley:
For learning C I'd suggest sticking to "The C Programming Language" and "Expert C Programming"...those IMO are the 2 best books on the subject

1 Like