4

I'm teaching myself a little C and have run across an exercise that I want to make sure I understand completely. The exercise is asking me to pass a pointer to a multidimensional array of integers to a function and iterate through the array. So I started with a print function before moving on to one taking input to populate the array. I tried all sorts of things but after finding a bit of code on type casting and iterating pointers I have the below program that seems to work but I'm not quite understanding what is going on. In my comments I have questions labeled 1-3, referring to the questions below the code. I was hoping someone more intelligent than myself could enlighten me.

//passing multi-Array to a simple print function
#include <stdio.h>

//simple print function prototype
void printMyArr(int (*pt)[4]);

int main()
{
    //declare and initialize a multi-array. I picked 4x4 and some arbitrary 
    //numbers for simplicity's sake

    int myArr[4][4] = { {12, 16, 19, 20}, 
                        {5, 99, 102, 200}, 
                        {12, 20, 25, 600},
                        {65, 66, 999, 1000} };

    //declare a pointer to an array of integers and an int for a loop counter
    int (*pt)[4], counter;

    //initialize the pointer
    pt = myArr;

    //for loop calling printMyArr function to iterate through arrays -- or this is what I understand it to mean
    for(counter=0; counter<4; counter++)
        printMyArr(pt++);   //<-------------Question 1
    return 0;
}

//function called to print array elements to the console
void printMyArr(int(*pt)[4])
{
    //declare a counter....and apparently another pointer
    int counter, *p;

    //initialize new pointer to old pointer and type cast array as int
    p = (int *)pt;          //<-------------Question 2

    //for loop to iterate through elements of array -- or this is what I understand it to mean
    for(counter=0; counter<4; counter++)
        printf("\n\n\n%d", *p++);  //<------Question 3
}

Question 1: Here, I've passed the pointer to the function and I keep thinking, "What is this loop iterating over?". Am I correct in thinking that I am incrementing the pointer to each of the first elements in each array (myArr[0][0], myArr[1][0], myArr[2][0], myArr[3][0])? Also, am I correct in assuming that the syntax of this line is in essence saying: "Execute the function passing the current pointer and THEN when it's done, increment the pointer."?

Question 2: This is what has me the most confused. After quite a bit of digging I found this bit to make it run right and I realize this is how it works, but why?

Question 3: Am I correct thinking that I am incrementing each element here?

So

1: pass pointer as assigned -> myArr[0][0] then print the values in myArr[0][0], myArr[0][1], myArr[0][2], and myArr[0][3], then increment pointer myArr[1][0]

2: pass pointer as assigned ->myArr[1][0] then print the values in myArr[1][0], myArr[1][1], myArr[1][2] and myArr[1][3] increment pointer to myArr[2][0]

3: pass pointer as assigned ->myArr[2][0] then print the values in myArr[2][0], myArr[2][1], myArr[2][2] and myArr[2][3] increment pointer to myArr[3][0]

4: pass pointer as assigned ->myArr[3][0] then print the values in myArr[3][0], myArr[3][1], myArr[3][2] and myArr[3][3] increment pointer to myArr[4][0] and if this is the case what is the pointer pointing to since there shouldn't be a myArr[4][0]?

Dan
  • 758
  • 6
  • 20
  • 2
    It's awesome to see such a thoroughly considered and well written question. Nicely done. – erik258 Jan 02 '19 at 19:45
  • 1
    Understand, on access `int myArr[3][5]` the first level of indirection (e.g. the first `[..]`) is converted to a pointer to the first element in the array. With a 2D array the first element is a pointer to the first row (array). So it is converted `(*myArr)[5]` which is a pointer-to-array of `int [5]`. You can't just use `*myArr[5]` as `[ ]` binds tighter than `'*'` from a precedence standpoint which would result in an array-of-pointers to `int` (5 pointers). With `(*myArr)[5]` the pointer advances by 20-bytes (5-int) per-increment, with `*myArr[5]` the pointer advances by `sizeof a_pointer`. – David C. Rankin Jan 02 '19 at 19:51
  • 2
    The reference in the standard is [C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)](http://port70.net/~nsz/c/c11/n1570.html#6.3.2.1p3) (note the exceptions when it is NOT converted to a pointer) – David C. Rankin Jan 02 '19 at 19:53

3 Answers3

2

Question 1: Here, I've passed the pointer to the function and I keep thinking, "What is this loop iterating over?". Am I correct in thinking that I am incrementing the pointer to each of the first elements in each array (myArr[0][0], myArr[1][0], myArr[2][0], myArr[3][0])?

more or less. pt starts at the address of myArr. You know there's 4 things in the array so you loop 4 times, incrementing pt after accessing it (read below) every time to pass to printMyArr each of the 4 elements of the "top level", "outer" array. Then printMyArr iterates over the 4 elements of the inner array to show each number.

Also, am I correct in assuming that the syntax of this line is in essence saying: "Execute the function passing the current pointer and THEN when it's done increment the pointer."?

Functionally, yes. Technically the order of operations looks like this:

1) get the value of pt 2) increment pt 3) call function, passing the previous value of pt from step 1

pt is incremented as part of the pt++ call, but pt++ evaluates to the old value. As an argument to a funciton, pt++ must be evaluated before the function it's passed to runs. But the timing looks the same as it evaluating directly after the function runs for your case.

Let's take questions 2 and 3 together because they're both part of the answer.

Question 2: This is what has me the most confused. After quite a bit of digging I found this bit to make it run right and I realize this is how it works, but why?

Question 3: Am I correct thinking that I am incrementing each element here?

p = (int *)pt;          
for(counter=0; counter<4; counter++)
    printf("\n\n\n%d", *p++);  

p stores an address of an integer. You set it to the address of the first element of the inner array (which is the address of the inner array itself, so to speak). Then, knowing you have 4 elements in the array, you'r able to loop 4 times, doing a similar pointer post-increment operation on p that you did with pt on the outer array.

The first iteration, p is the same as pt, the address of the first of 4 integer values in memory. *p, dereferencing the pointer, gets the first integer. *p++, due to order of operations ( ++ being of the highest precedence, and dereferencing * being lower), returns the integer at p, leaving p pointing to the next integer address for the next loop.

In general, casting values in C should be avoidid whenever possible. You merely have to do a dereference here to point p to the set of integers. pt holds the address of one of 4 addresses ( outer array ) of contiguous sets of 4 integers (inner array) in memory. The value at *pt is the address of the "current" set of 4 contiguous integers in memory. So you can simply say,

int* p=*pt;

Which doesn't arbitrarily cast a type and is very straightforward and clear. (Thanks @WhozCraig)

erik258
  • 14,701
  • 2
  • 25
  • 31
  • ...and on the last iteration? The compiler knows not to increment the pointer to point at the non-existent `myArr[4][0]` or is that included in the Seems like to me given the order of operations the pointer is incremented before the loop can test `counter` and evaluate it to <4 – Dan Jan 02 '19 at 20:13
  • 4
    Question #2: The proper syntax *without* the hard cast (avoided by seasoned programmers, nearly always a sign of misunderstanding or masking type issues by beginners), is to use `int *p = *pt;` – WhozCraig Jan 02 '19 at 20:14
  • 1
    That's a great question. Actually `pt` and `p` _are_ post-incremented to the first "non-existent" address on the last iteration of the loop, but they're not dereferenced again because the `for` loop's condition is violated on the 5th pass, when an out-of-bounds `p` or `pt` would be dereferenced . So though the last value of `pt` and `p` is an invalid one, that value is never actually accessed. – erik258 Jan 02 '19 at 20:16
  • @danFarrell well...ive been reading a lot about pointing at nothing and how it can really mess up your day, so ive been ultra sensitive to it...but thank you! I appreciate your answers! – Dan Jan 02 '19 at 20:17
  • @whozcraig I need to digest and research what you've said. I'm not sure Im understanding but Im going to try. I appreciate the input! – Dan Jan 02 '19 at 20:19
  • @Dan - [2 versions of your `prnMyArr`](https://pastebin.com/CeNYUChE) that may offer a bit more exercise of the issue. – David C. Rankin Jan 02 '19 at 20:27
  • @whozcraig and @danFarrell, so I think I understand. no need to type cast. what if I didn't bother with the declaration for the pointer and just de-referenced in the `'printf()` statement? This is where I was having trouble before and it was giving me the addresses instead of the values so maybe I hadn't done it quite right... – Dan Jan 02 '19 at 20:27
  • 2
    @Dan First, pointers in C *point* to stuff (bear with me). You *dereference* a pointer to get to what it points to. The underlying *type* of the thing being pointed to dictates many things, among them outcome of *pointer arithmetic*. If you have some `int *p`, then `*p` gets you the `int` being pointed-to, and `++p` (or p++ post-facto) advances the pointer to the next `int`. That said, your function argument is a pointer to an *array* `[4]` of `int`. That's important, because that mean `++p` will advance to the next `int[4]` (not what you want to do). That's probably where the wheels came off. – WhozCraig Jan 02 '19 at 20:34
  • Right @WhozCraig. I think I’ve got the dereference thing...I just have a hard time keeping it straight! Lol – Dan Jan 02 '19 at 20:41
1

A multy D array is kept in memory as a continuous array so if you have an array [2][2] it will allocate 4 continuous sizeof(int). The reason you need to define array[][COL] is to let the compiler know how the pointer arithmetic should be done.

so- for Q1- you are right, each time you increment you do so for(sizeof(int)*COL). so each time you move by a row.

Q2- now for each row you want to print one int value at a time, so when you have int* p- this means each increment is done by sizeof(int).

Q3- the increment is for the pointer- not the value- its easy to see for yourself if you do another print in your main.

Hope this help, good luck!

H.cohen
  • 517
  • 3
  • 9
0

The in-memory view of 2D myArr array would be something like this:

        myArr
         ---------------------
myArr[0] | 12 | 16 | 19 | 20 |
         ---------------------
myArr[1] | 5  | 99 |102 |200 |
         ---------------------
myArr[2] | 12 | 20 | 25 |600 |
         ---------------------
myArr[3] | 65 | 66 |999 |1000|
         ---------------------

Question 1: Here, I've passed the pointer to the function and I keep thinking, "What is this loop iterating over?". Am I correct in thinking that I am incrementing the pointer to each of the first elements in each array (myArr[0][0], myArr[1][0], myArr[2][0], myArr[3][0])? Also, am I correct in assuming that the syntax of this line is in essence saying: "Execute the function passing the current pointer and THEN when it's done, increment the pointer."?

Answer of question 1:

int (*pt)[4]

pt is pointer to an array of 4 integer. This statement

pt = myArr;

makes the pt pointing to first 1D array of myArr 2D array. It is similar to:

pt = &myArr[0];

The impact of both the statement will be like this:

pt--------|
         \|/
         ---------------------
myArr[0] | 12 | 16 | 19 | 20 |
         ---------------------

When you increment a pointer, it gets incremented in steps of the object size that the pointer can point to. Here

printMyArr(pt++);

first the pt pointer value passed to printMyArr() and then pt will be incremented and since its type is int (*)[4] that means it can point to an object which is an array of 4 integers, so after increment the pt will point to the address (which is address pointing to + size of array of 4 int) i.e. to myArr[1] address.

Note that this:

for(counter=0; counter<4; counter++)
        printMyArr(pt++);

is same as this:

for(counter=0; counter<4; counter++)
        printMyArr(&myArr[counter]);
                   ^^^^^^^^^^^^^^^

Question 2: This is what has me the most confused. After quite a bit of digging I found this bit to make it run right and I realize this is how it works, but why?

Answer of question 2:

Here

p = (int *)pt;   // pt is pointer to an array of `4` integer

if you remove the (int *) cast, you will find that the compiler is giving a warning message on this statement. The reason is p and pt are not the compatible pointer types. The type of p is int * whereas the type of pt is int (*)[4]. This incompatible pointer assignment is working because the address of an array and address of first element of an array is numerically same though their type is different. Instead, you should do

p = &(*pt)[0];

From C Standards#6.5.2.1

The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2)))

by this rule

&(*pt)[0] --> &(*((*pt)+(0))) --> ((*p)+(0)) --> *p

The operator & is used to get the address and the operator * is used for dereferencing. These operators cancel the effect of each other when used one after another.

Hence, this statement

p = &(*pt)[0];

is same as this statement

p = *pt;

Question 3: Am I correct thinking that I am incrementing each element here?

Answer of question 3:

Yes.
p is a pointer pointing to first element of array of 4 integer and its type is int * i.e. it can point to an object which is an int. When you are doing *p++, this will first dereference p and get the value at the address it is pointing to then the p will be incremented and pointing to next element of array.

H.S.
  • 11,654
  • 2
  • 15
  • 32