Daniel
Daniel

Reputation: 369

Pointer to 2D Array(why does it work)

I have the following function ( I want to print all elements from a given row)

void print_row(int j, int row_dimension, int *p)
{   
p = p + (j * row_dimension);

for(int i = 0; i< row_dimension; i++)
    cout<<*(p+i)<< " ";
}

Creating an array

int j[3][3]={{1,2,3},
             {4,5,6},
             {7,8,9} };

What I do not understand is why can I call the function in the following way :

print_row(i, 3, *j);

Why can I give as a parameter "*j" ? Shouldn't an address be passed? Why can I use the indirection operator?

Upvotes: 2

Views: 99

Answers (4)

Serge Ballesta
Serge Ballesta

Reputation: 148890

j is in fact an array of arrays. As such, *j is an array of three integers, and when used as a rvalue, it decays to a pointer to its first element, said differently, it decays to &j[0][0].

Then in printrow you compute the starting address of the first element of each subarray - that's the less nice part, I'll come back later to it. Then you correctly use the *(p+i) equivalent of p[i] to access each element of the subarray.


The remaining part of the answer is my interpretation of a strict reading of C standard

I said that computing the starting address of each subarray was the less nice part. It works because we all know that a 2D array of size NxM has the same representation in memory as a linear array of size N*M and we alias those representations. But if we respect strictly the standard, as an int pointer, &p[i][j] points to the first element of an array of three elements. As such, when you add the size of a row, you point past the end of the array which leads to undefined behaviour if you later dereference this address. Of course it works with all common compilers, but on an old question of mine, @HansPassant gave me a reference on experimental compilers able to enforce controls on arrays sizes. Those compilers could detect the access past the end of the array and raise a run time error... but it would break a lot of existing code!

To be strictly standard conformant, you should use a pointer to arrays of 3 integers. It requires the use of Variable Length Arrays, which is an optional feature but is fully standard conformant for system supporting it. Alternatively, you can go down to the byte representation of the 2D array, get its initial address, and from there compute as byte addresses the starting point of each subarray. It is a lot of boiling plate address computations but it fully respect the @#!%$ strict aliasing rule...

TL/DR: this code works with all common compilers, and will probably work with a lot of future versions of them, but it is not correct in a strict interpretation of the standard.

Upvotes: 2

Havenard
Havenard

Reputation: 27854

Memory isn't multidimensional, so even if its a 2D array, it's data will be placed in a sequential manner, so if you get a pointer to that array -- that is implicitly a pointer to the first element of it -- and start reading the elements sequentially, you will iterate through all elements of this 2D array, reading element from the subsequent rows just after the last element of the previous one.

Upvotes: 1

dkolmakov
dkolmakov

Reputation: 657

Your code works because *j is a pointer which has the same value as j or j[0]. Such behavior caused by mechanics of how two-dimensional arrays are handled by the compiler.

When you declare 2D array:

int j[3][3]={{1,2,3},
             {4,5,6},
             {7,8,9}};

compiler actually puts all values sequentially in memory, so the following declaration will have the same footprint:

int j[9]={1,2,3,4,5,6,7,8,9};

So in your case pointers j, *j and j[0] just point to the same place in memory.

Upvotes: 2

wally
wally

Reputation: 11002

int j[3][3] = 
{{1,2,3},
{4,5,6},
{7,8,9}}; // 2d array

auto t1 = j; // int (*t1)[3]
auto t2 = *j; // int *t2

So what is happening is that *j produces j[0], which is an int[3] which then decays to an int*.

Upvotes: 3

Related Questions