0

I know there are many similar questions, but I can't find an answer.

When passing the two-dimensional [3] [4] array to the function in my code below, how does the compiler know how far to increment the pointer, in the case of the last printf() where we are incrementing 3 x 4 memory locations, if the number 3 is missing in the function argument? I mean, why is only arr [] [4] sufficient and not [3] [4]? Thanks

#include <stdio.h>
#include <stdlib.h>

int Fun(int arr[][4])
{
    printf("%p\n", arr);         // address of first element
    printf("%p\n", arr[0] + 1);  // address increments by 4, pointing to next "inner array" 
    printf("%p\n", arr + 1);     // how does it know to increment address by 3 x 4 here? The complete array size
}

int main()
{
    int arr[3][4] =
    {
        1,2,3,4,
        5,6,7,8,
        9,10,11,12
    };

    printf("%p\n", arr);
    printf("%p\n", arr[0] + 1);
    printf("%p\n", arr + 1);

    printf("Passing to function\n");

    Fun(arr);

    return 0;

    }
Engineer999
  • 3,683
  • 6
  • 33
  • 71
  • I guess `sizeof (int)` is 4 in your implementation. Thing is, you are incrementing 3 x `sizeof (int)` **bytes** (not 3 * 4 * `sizeof (int)` bytes). Try with `int arr[3][7] = ...` – pmg Aug 18 '18 at 13:38
  • I don't understand – Engineer999 Aug 18 '18 at 13:47
  • let's say your element with value 1 is at address 0x66600000. then your element with value 2 is at 0x66600000 + `sizeof (int)` (probably 0x66600004) – pmg Aug 18 '18 at 13:54
  • That part I understand, then arr[0] + 1, will be 0x6660000C because we are incrementing as 0x66600000 + ( 4 * sizeof (int) )... but arr + 1 will increment as 0x66600000 + 3 * 4 * sizeof(int), but how does the compiler know inside the function about the full size of the 2 dimensional array being 3 * 4 * sizeof(int) ? – Engineer999 Aug 18 '18 at 14:03
  • [It doesn't](https://ideone.com/41fVKr). Please note the error message here: https://wandbox.org/permlink/45ANpp3tESh3Dkqg , you have an extra level of indirection. – Bob__ Aug 18 '18 at 14:05
  • I get a warning on the `Fun(arr)` line: *warning: incompatible pointer types passing 'int [3][4]' to parameter of type 'int *(*)[4]'* - you should get rid of all warnings before reading too much into generated pointers. – cdarke Aug 18 '18 at 14:08
  • Hmmm ... looking better at your code I noticed an type mismatch your compiler probably warned you about. You defined `Fun()` as taking a pointer to an array of unspecified size of arrays of 4 ints, but you pass to it a pointer to an array of 4 ints. Try a clean compilation (**turn on all warnings**) before understanding executable output. – pmg Aug 18 '18 at 14:09
  • Some things are wrong with your question. First, none of the `printf` statements show an address that has been incremented by any multiple of 3. You should show the actual output you got. Second, your array is defined in `main` to be an array of arrays of `int`, but the `Fun` parameter is an array (which is adjusted to be a pointer) to an array of `int *`. That `*` does not belong there. You might have intended the parameter to be a pointer to an array, but that is not needed, for reasons to long to discuss in this comment. – Eric Postpischil Aug 18 '18 at 14:12
  • Check this out: https://stackoverflow.com/questions/12813494/why-do-we-need-to-specify-the-column-size-when-passing-a-2d-array-as-a-parameter – subdeveloper Aug 18 '18 at 14:13
  • @EricPostpischil Yes, the * was a typo, it should be int [][4] just. Yes, my printf()'s are showing an increment by 4 * 3 – Engineer999 Aug 18 '18 at 14:19
  • @Engineer999: Your `printf` statements are not showing an increment of 4•3. If you believe they are, paste the **exact output** into the question. – Eric Postpischil Aug 18 '18 at 14:25
  • C have only (1D) arrays. They might be arrays of arrays, or arrays of aggregates, or arrays of pointers. But C don't have 2D arrays. – Basile Starynkevitch Aug 18 '18 at 14:25
  • You could have seen a 12-byte difference between `arr[0] + 1` and `arr + 1`. This is not a 12-byte increment of one object. It is the distance from `arr[0][1]` to `arr[1][0]`, so it is 16 bytes minus four bytes. – Eric Postpischil Aug 18 '18 at 15:55
  • @EricPostpischil Yes, that was where I got it wrong. Thanks – Engineer999 Aug 18 '18 at 16:07

2 Answers2

1

First, Fun should be defined with:

int Fun(int arr[][4])

rather than what you have, int Fun(int* arr[][4]);.

Next, when Fun(arr) is evaluated, arr is automatically converted from an array of 3 arrays of 4 int to a pointer to an array of 4 int. Similarly, in the declaration of Fun, int arr[][4] is automatically adjusted to be a pointer to an array of 4 int. So the argument type and the parameter type will match if you declare Fun correctly.

You could also declare Fun as:

int Fun(int (*arr)[4])

This is the same thing as above, due to the automatic adjustment that would be applied to the declaration above. Note that the asterisk here is grouped with the arr by the parentheses. This makes it a pointer to an array of int, rather than an array of pointers to int.

Now, as to what will be printed, in main:

printf("%p\n", arr);

In this statement, arr will be automatically converted to a pointer to its first element, so it becomes a pointer to an array of 4 int. Then the value of this pointer is printed. Note: When printing pointers, technically you should convert them to const void * or void *, as with printf("%p\n", (const void *) arr);. However, omitting this likely does not cause a problem at the moment.

printf("%p\n", arr[0] + 1);

In this statement, arr[0] is the first element of arr. That first element is an array of 4 int, and it is automatically converted to be a pointer to its first element. So arr[0] becomes a pointer to the first int. Then adding 1 advances the pointer to the next int. The result is likely an address four bytes beyond arr, depending on your C implementation. (It could be a different number of bytes, but four is the most common today.)

printf("%p\n", arr + 1);

In this statement, arr is converted to a pointer to its first element, an array of 4 int. Adding 1 advances to pointer to the next element, which is the next array of 4 int. So this likely adds 16 bytes to the address.

Then, in Fun:

printf("%p\n", arr);         // address of first element

Here arr is a pointer to an array of 4 int. Its value is printed, yielding the same address as for the corresponding printf in main.

printf("%p\n", arr[0] + 1);  // address increments by 4, pointing to next "inner array" 

Here arr[0] is the object pointed to by arr, which is an array of 4 int. Since it is an array, it is automatically converted to a pointer to its first element, which is an int. So this points to the first int. Then adding 1 advances to the next int, and this again yields the same address as the corresponding printf in main.

printf("%p\n", arr + 1);     // how does it know to increment address by 3 x 4 here? The complete array size

In this case, arr is a pointer to an array of 4 int, and adding 1 advances it to the next array of 4 int, so the result is likely 16 bytes beyond the value of arr, and this again yields the same address as the corresponding printf in main.

If you saw different values for the printf statements in Fun and main, this was likely because of the incorrect declaration with int* and because int * is eight bytes in your C implementation, compared to four for int. That error would have doubled some of the increments. You should not have seen any multiple of three in the increments.

Regarding the first dimension, Fun does not need to know the first dimension because it never advances any pointers by units of the first dimension. It receives only a pointer to an array of 4 int, and it does not need to know that there are 3 such arrays there.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

The detailed answer by Eric Postpischil clearly shows all the issues in OP's code.

I'd like to note that passing a pointer to the correct type would let the compiler doing the right pointer arithmetic:

#include <stdio.h>
#include <stdlib.h>

void Fun(int (*arr)[3][4])
{
    printf("Address of the first element:   %p\n", (void *)*arr);
    printf("Address of the second row:      %p\n", (void *)(*arr + 1));
    printf("Address after the last element: %p\n", (void *)(arr + 1));
}

void Fun_vla(size_t rows, size_t cols, int (*arr)[rows][cols])
{
    printf("Address of the first element:   %p\n", (void *)*arr);
    printf("Address of the second row:      %p\n", (void *)(*arr + 1));
    printf("Address after the last element: %p\n", (void *)(arr + 1));
}

int main()
{
    int arr[3][4] =
    {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };

    Fun(&arr);
    puts("");
    Fun_vla(3, 4, &arr);
    return 0;
}
Bob__
  • 12,361
  • 3
  • 28
  • 42
  • Thanks. That's interesting. In this case the compiler knows the full size of the array. It was my confusion from earlier, in that how can the compiler know about the 48 bytes offset if it doesn't know about [3][4], but it didn't. Why do you cast to (void *) in the printf()'s tho – Engineer999 Aug 18 '18 at 15:20
  • @Engineer999 About that: https://stackoverflow.com/questions/24867814/printfp-and-casting-to-void – Bob__ Aug 18 '18 at 15:27