0

I am trying to understand the following code.

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

void print2(int (* a)[2]) {
    int i, j;
    for (i = 0; i < 3; i++ ) {
        for (j = 0; j < 2; j++ ) {
            printf("%d", a[i][j]);
        }
    printf("\n");
    }
}

void print3(int (* a)[3]) {
    int i, j;
    for (i = 0; i < 2; i++ ) {
        for (j = 0; j < 3; j++ ) {
            printf("%d", a[i][j]);
        }
    printf("\n");
    }
}

int main() {
    int a[] = { 1, 2, 3, 4, 5, 6 };
    print2((int (*)[2]) a);
    print3((int (*)[3]) a);
    return 0;
}

Running the code returns following output in console:

12
34
56
123
456

My problem is I don't understand where these numbers come from. I have trouble understanding what is actually going on in this code. More specifically, I'm uncertain of what this means:

int( (* a)[2])

I hope someone can explain this code to me, because I really want to understand how pointers and multidimensional arrays work in C.

Carl
  • 149
  • 1
  • 7
  • I suggest section 6 of the [comp.lang.c faq](http://c-faq.com/). – pmg Apr 21 '20 at 09:17
  • If you do not understand the numbers, you should probably add a space after the `%d` in the `printf`. As your statement is it will print both (or all 3 in the other function) in a row without a space in it. So `1` and `2` becomes `12`. – mch Apr 21 '20 at 09:28

4 Answers4

1

void print2(int (*a)[2]) { /*...*/ }

inside the function print2 a is a pointer to arrays of 2 ints

void print3(int (*a)[3]) { /*...*/ }

inside the function print3 a is a pointer to arrays of 3 ints

int a[] = {1, 2, 3, 4, 5, 6};

inside the function main a is an array of 6 ints.
In most contexts (including function call context) a is converted to a pointer to the first element: a value of type "pointer to int".

The types "pointer to int", "pointer to array of 2/3 ints" are not compatible, so calling any of the functions with print2(a) (or print3(a)) forces a diagnostic from the compiler.

But you use a cast to tell the compiler: "do not issue any diagnostic. I know what I'm doing"

   print3(a); // type of a (after conversion) and type of argument of print3 are not compatible
// print3((cast)a); // I know what I'm doing
   print3((int (*)[3])a); // change type of a to match argument even if it does not make sense
pmg
  • 106,608
  • 13
  • 126
  • 198
1

It would be much easier to understand if you break it down and understand things. What if you were to pass the whole array to say a function print4, which iterates over the array and prints the elements? How would you pass the array to such a function.

You can write it something like

print4( (int *) a); 

which can be simplified and just written as print4(a);

Now in your case by doing print2((int (*)[2]) a);, you are actually designing a pointer to an array of 2 int elements. So now the a is pointer in array of two elements i.e. every increment to the pointer will increase the offset by 2 ints in the array a

Imagine with the above modeling done, your original array becomes a two dimensional array of 3 rows with 2 elements each. That's how your print2() element iterates over the array a and prints the ints. Imagine a function print2a that works by taking a local pointer to a and increments at each iteration to the point to the next two elements

void print2a(int (* a)[2]) {
    int (* tmp)[2] = a;
    for( int i = 0; i < 3; i++ ) {
        printf("%d%d\n", tmp[0][0], tmp[0][1] );
        tmp++;
    }
}

The same applies to print3() in which you pass an pointer to array of 3 ints, which is now modeled as a 2-D array of 2 rows with 3 elements in it.

Inian
  • 80,270
  • 14
  • 142
  • 161
1

TL;DR

This code contains incorrect and meaningless hacks. There is not much of value to learn from this code.

Detailed explanation follows.


First of all, this is a plain 1D array that gets printed in different ways.

These lines are strictly speaking bugs:

print2((int (*)[2]) a);
print3((int (*)[3]) a);

In both cases there is an invalid pointer conversion, because a is of type int[6] and a pointer to the array a would have to be int (*)[6]. But the print statements are wrong in another way too, a when used in an expression like this "decays" into a pointer to the first element. So the code is casting from int* to int(*)[2] etc, which is invalid.

These bugs can in theory cause things like misaligned access, trap representations or code getting optimized away. In practice it will very likely "work" on all mainstream computers, even though the code is relying on undefined behavior.


If we ignore that part and assume void print2(int (*a)[2]) gets a valid parameter, then a is a pointer to an array of type int[2].

a[i] is pointer arithmetic on such a type, meaning that each i would correspond to an int[2] and if we had written a++, the pointer would jump forward sizeof(int[2]) in memory (likely 8 bytes).

Therefore the function abuses this pointer arithmetic on a[i] to get array number i, then do [j] on that array to get the item in that array.


If you actually had a 2D array to begin with, then it could make sense to declare the functions as:

void print (size_t x, size_t y, int (*a)[x][y])

Though this would be annoying since we would have to access the array as (*a)[i][j]. Instead we can use a similar trick as in your code:

void print (size_t x, size_t y, int (*a)[x][y])
{
  int(*arr)[y] = a[0];
  ...
  arr[i][j] = whatever; // now this syntax is possible

This trick too uses pointer arithmetic on the array pointer arr, then de-references the array pointed at.

Related reading that explains these concepts with examples: Correctly allocating multi-dimensional arrays

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • The part I'm trying to figure out is doesn't the code `arr[i][j]` functionally work out to `*(*(arr + i) + j)`. Aren't we losing a deference somewhere? Why doesn't this code dereference the pointer, and the second dereference attempt to dereference an integer? Put simply, why does `a == a[0]` inside the print function? I checked inside a debugger and I don't understand. – nickelpro Apr 21 '20 at 09:53
  • The *expression* `arr[i]` has type `int [y]`, which "decays" to a pointer type `int *` and evaluates to the address of the first element of the `i`'th subarray. It works the same way for "true" 2D arrays (declared as `T a[N][M]`) - there's no pointer in the array itself, the array *expressions* are converted to pointers as needed. – John Bode Apr 21 '20 at 13:00
  • @JohnBode Yes but in order to get to the decay part, you must first do `*(arr + i)`, where the pointer arithmetic is done on type `int(*)[y]`. This pointer arithmetic would be the implicit "x" dimension. – Lundin Apr 22 '20 at 06:51
1

The code seeks to reinterpret the array int a[6] as if it were int a[3][2] or int a[2][3], that is, as if the array of six int in memory were three arrays of two int (in print2) or two arrays of three int (in print3).

While the C standard does not fully define the pointer conversions, this can be expected to work in common C implementations (largely because this sort of pointer conversion is used in existing software, which provides motivation for compilers to support it).

In (int (*)[2]) a, a serves as a pointer to its first element.1 The cast converts this pointer to int to a pointer to an array of two int. This conversion is partially defined C 2018 6.3.2.3 7:

  • The behavior is undefined if the alignment of a is not suitable for the type int (*)[2]. However, compilers that have stricter alignment for arrays than for their element types are rare, and no practical compiler has a stricter alignment for an array of six int than it does for an array of two or three int, so this will not occur in practice.
  • When the resulting pointer is converted back to int *, it will compare equal to the original pointer.

The latter property tells us that the resulting pointer contains all the information of the original pointer, since it must contain the information needed to reconstruct the original pointer. It does not tell us that the resulting pointer is actually pointing to the memory where a is.

As noted above, common C implementations allow this. I know that Apple’s versions of GCC and Clang support this reshaping of arrays, although I do not know whether this guarantee was added by Apple or is in the upstream versions.

Given that (int (*)[2]) is passed to print2 as its a, then a[i][j] refers to element j of array i. That is, a points to an array of two int, so a[0] is that array, a[1] is the array of two int that follows it in memory, and a[2] is the array of two int after that. Then a[i][j] is element j of the selected array. In effect, a[i][j] in print2 is a[i*2+j] in main.

Note that no aliasing rules are violated as no arrays are accessed by a[i][j]: a is a pointer, a[i] is an array but is not accessed (it is automatically converted to a pointer, per footnote 1 below), and a[i][j] has type int and accesses an object with effective type int, so C’s aliasing rules in C 2018 6.5 7 are satisfied.

Footnotes

1 This is because when an array is used in an expression, it is automatically converted to a pointer to its first element, except when it is the operand of sizeof, is the operand of unary &, or is a string literal used to initialize an array.

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