Two-Dimensional Arrays and Pointers

One Dimensional Arrays and Pointers

Let's start with something simple:
double d = 5.0;  /* a double              */
double *pd = &d; /* a pointer to a double */
Visually (with an arbitrary address):
This is the simplest use of a pointer. Here, d is a double and pd is a pointer to it. Nothing complicated at all.

When dealing with pointers and arrays, things can seem a little complicated. For example, there is a big difference between a pointer to a double and a pointer to an array of doubles. Below, da is an array of 4 doubles:

double da[4] = {1.0, 2.0, 3.0, 4.0}; /* array of 4 doubles */
Visually, with an arbitrary address of 100:
How we declare the pointer is important. Creating a pointer to a double is trivial:
double *pd = da; /* a pointer to a double (in this case it's a pointer to the first element of the array) */
Visually:
What confuses many new C programmers is that pd is not pointing at the array, but is pointing at the first element of the array. When an array name is used in an expression, the array is said to decay into a pointer to the first element. You can read about the history (and gory details) here: The Development of the C Language under the section: Embryonic C.

The exception to this rule is when the array name is used with the sizeof operator. (Actually, there are two more exceptions: When the array name is used with the & (address-of) operator and literal strings used to initialize an array. See this for more details.)

This is just how the language works and is something that C programmers have to get used to.

Also, remember that these are equivalent:

double *pd = da;     /* a pointer to a double (the first element of the array) */
double *pd = &da[0]; /* a pointer to a double (the first element of the array) */
double *pd = da + 0; /* same as the others, but kind of strange to do this     */
If we wanted to point at the second element, we would do this:
double *pd = &da[1]; /* a pointer to a double (the second element of the array) */
Visually:
These are equivalent:
double *pd = &da[1]; /* a pointer to a double (the second element of the array) */
double *pd = da + 1; /* a pointer to a double (the second element of the array) */

Creating a pointer to an array of doubles is a little more complicated:

double (*pda)[4] = &da; /* a pointer to an array of 4 doubles */
Visually:
What is sizeof(pda)? What is sizeof(*pda)? What is sizeof(**pda)? To answer these questions you must first answer the question: "What are the types of each of those objects?"

The parentheses around *pda are important. Without them, you have this:

double *pda[4] /* an array of 4 pointers to doubles */
which is certainly not what you want. Also, the & in front of da is important. Without it, it's incorrect:
double (*pda)[4] = da; /* Incorrect */
and the gcc compiler calls us out on it:
warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
   double (*pda)[4] = da;
 
The clang compiler is more verbose, even telling you how to fix it:
warning: incompatible pointer types initializing 'double (*)[4]' with an expression of type 'double [4]'; 
         take the address with & [-Wincompatible-pointer-types]
  double (*pda)[4] = da;
           ^         ~~
                     &

Two Dimensional Arrays and Pointers

Here's the reference array initialized with values:
double points[3][4] = { 
                        {1.0,  2.0,  3.0,  4.0}, 
                        {5.0,  6.0,  7.0,  8.0}, 
                        {9.0, 10.0, 11.0, 12.0}
                      };
Visualized with an arbitrary address:
and with subscripts and arbitrary addresses:


The concept of creating pointers is similar with 2-dimensional arrays. If we want a pointer to the 2D array of doubles above, we could do this:

double (*p1)[3][4] = &points; /* pointer to an array of 3 arrays of 4 doubles */
and it would look like this visually:

Again, the & in front of points is important. This is incorrect:

double (*p1)[3][4] = points; /* Incorrect */
and the gcc compiler calls us out on it:
warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
   p1 = points; 
      ^
The clang compiler is more verbose:
warning: incompatible pointer types assigning to 'double (*)[3][4]' from 'double [3][4]'; 
         take the address with & [-Wincompatible-pointer-types]
  p1 = points;
     ^ ~~~~~~
       &

This may seem odd at first, especially if you are thinking that the value of points and &points are the same. This is true. They have the same value but they are of different types, and that's what the compiler is checking: types, not values. The compiler can't check values because that's done at runtime.

That is also why these are incorrect:
double (*p1)[3][4] = &points[0];    /* Incorrect */
double (*p1)[3][4] = &points[0][0]; /* Incorrect */
And this is what the clang compiler says:
warning: incompatible pointer types assigning to 'double (*)[3][4]' from 'double (*)[4]' [-Wincompatible-pointer-types]
  p1 = &points[0];    /* Incorrect */
     ^ ~~~~~~~~~~
warning: incompatible pointer types assigning to 'double (*)[3][4]' from 'double *' [-Wincompatible-pointer-types]
  p1 = &points[0][0]; /* Incorrect */
     ^ ~~~~~~~~~~~~~
It's incorrect because the types are different: In fact, all of these expressions have the same value but are of different types:
ExpressionType
pointsan array of 3 arrays of 4 doubles.
&pointsthe address of (pointer to) an array of 3 arrays of 4 doubles.
&points[0]the address of (pointer to) an array of 4 doubles (the first row).
&points[0][0]the address of (pointer to) a double (the first double in the first row).
Don't believe me? Here's a Mead Proof:
printf("Value of points is        %p\n", (void *)points);
printf("Value of &points is       %p\n", (void *)&points);
printf("Value of &points[0] is    %p\n", (void *)&points[0]);
printf("Value of &points[0][0] is %p\n", (void *)&points[0][0]);
Output:
Value of points is        0x7fffaa8fc240
Value of &points is       0x7fffaa8fc240
Value of &points[0] is    0x7fffaa8fc240
Value of &points[0][0] is 0x7fffaa8fc240
Those are stack-based addresses on a 64-bit Linux computer.


If we want to create a pointer to one element (row) of the 2D array, we could do this:

double (*p2)[4] = &points[1]; /* pointer to an array of 4 doubles (2nd element/row) */
Visually:
These are equivalent:
double (*p2)[4] = &points[1]; /* pointer to an array of 4 doubles (2nd element) */
double (*p2)[4] = points + 1; /* pointer to an array of 4 doubles (2nd element) */
If we want to create a pointer to the first element of that row, we could do this:
double *p3 = &points[1][0]; /* a pointer to a double (first element of 2nd row) */
Visually:
Notice how the value (132) of p2 and p3 are the same. Their values are the same, but the types are different.

If we want to create a pointer to the third element of that row, we could do this:

double *p3 = &points[1][2]; /* a pointer to a double (3rd element of 2nd row) */
Visually:
If we wanted to use the pointer p2 from above to initialize p3, it would look like this:
double *p3 = &(*p2)[2]; /* a pointer to a double (3rd element of 2nd row) */
Breaking it down (as the compiler does):
  1. p2 is a pointer to an array of 4 doubles.
  2. *p2 is an array of 4 doubles.
  3. (*p2)[2] is a double (the 3rd double) in the array (parentheses required).
  4. &(*p2)[2] is the address of the 3rd double in the array.
  5. Remember, you can use the phrase address of and pointer to interchangeably.
  6. Easy, peasy, lemon squeezy!
Knowing the types of those expressions above will help you answer the question: What is sizeof each of those expressions?
  1. 8 (sizeof a pointer)
  2. 32 (sizeof 4 doubles)
  3. 8 (sizeof a double)
  4. 8 (sizeof a pointer)