0

I am trying to run this code but there is a compiler warning:

for the statement invoking the print function

print(arr,3,4);

passing argument 1 of 'print' from incompatible pointer type [-Wincompatible-pointer-types] main.c /arrays line 23 C/C++ Problem

for the printf statement:

format '%d' expects argument of type 'int', but argument 2 has type 'int *' [-Wformat=] main.c /arrays line 6 C/C++ Problem

The code is to pass a 2D array to a function which receives it as a double pointer and print it inside the function.

void print (int **A, int m, int n){
    for(m = 0; m < 3; m++){
            for(n = 0; n < 4; n++){
                    printf("%d  ", *((A+(m * 4) + n)));
                }
                printf("\n");
        }
}

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

    print(arr,3,4);

    return 0;
}

1) Passing a 2D array as a double pointer is incorrect in C? here The above link points only to C++? or this is possible in C as well?

2) If I am using a single pointer then which of the following assignments are correct/incorrect?

Listing 1:

int *ptr;
    ptr = &arr[0][0];
    for(int i = 0; i < 3; i++){
                for(int j = 0; j < 4; j++){
                        printf("%d  ", *(ptr + (i * 4) +j));
                    }
                    printf("\n");
            }

Listing 2:

int * ptr; ptr = arr;

Eric Postpischil
  • 141,624
  • 10
  • 138
  • 247
hago
  • 159
  • 7
  • The error message is correct; but your function prototype is not. Are you in fact passing an array of pointers? – Jongware Jul 07 '18 at 22:47
  • apparently the memory layout of the array is actually the same as a 1 dimensional array. This code: https://pastebin.com/6hk63y9W prints the numbers in sequence. I could have sworn this wasn't how C compiled 2D arrays but I guess I was wrong. If you try to treat it as an array of pointers to arrays you will get a seg fault. What you can do is make an array of pointers, then assign arrays to those pointers. Initializing it statically will not do what you were intending. – Chris Rollins Jul 08 '18 at 00:37

3 Answers3

2

You expectation that a 2D array will decay to a pointer to a pointer is ill-founded.

To be able to use arr as an argument to print, you have the following options.

  1. Change print to

    void print (int (*A)[4], int m){ // Not need for n. It is 4
    
  2. Change print to use a VLA. For this to work, m and n have to come before A.

    void print(int m, int n, int A[m][n] {
    

Both these changes will require you to change the call also.

R Sahu
  • 196,807
  • 13
  • 136
  • 247
2

Let’s start with:

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

print(arr,3,4);

In print(arr,3,4);, arr is an array. Specifically, it is an array of 3 elements, each of which is an array of 4 elements, each of which is an int. Thus, arr is an array of 3 arrays of 4 int. You have probably heard or read that arrays “decay” to pointers. This is a colloquial term. The actual rule, which you can find in clause 6.3.2.1, paragraph 3, of the C 2011 standard, is:

Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type “array of type” is converted to an expression with type “pointer to type” that points to the initial element of the array object and is not an lvalue.

Here is how this rule applies to arr in print(arr,3,4);:

  • arr is an identifier, meaning it is the name of some object. As such, it designates its object. That object is an array of 3 arrays of 4 int.
  • This array is not the operand of sizeof or Alignof or &, and it is not a string literal. So, following the rule, it is converted from an array of 3 arrays of 4 int to a pointer to the first array of 4 int.

What happens next? Nothing. The expression we have is a pointer to an array of 4 int. There is no rule that says a pointer to an array is converted to a pointer to a pointer. We have a pointer to an array, but that array is not used in an expression yet, not in the simple expression arr. So it is not converted.

This means what you are passing to print is a pointer to an array of 4 int. But your declaration for print says it takes a pointer to a pointer to an int. Those are different things, and they are incompatible, so the compiler warns you.

(To see they are incompatible, consider the difference to a pointer to an array of 4 int and a pointer to a pointer to int. The memory at a pointer to an array of 4 int contains 4 int values. The memory at a pointer to a pointer to an int contains a pointer. These are very different things.)

Next, consider:

void print (int **A, int m, int n)
…
    printf("%d  ", *((A+(m * 4) + n)));

We know from above that you ought to change int **A to int (*A)[4], which is a pointer to an array of 4 int. You can also change it to int A[][4], because there is a rule in C that such a parameter declaration will be automatically adjusted to be int (*A)[4], as a convenience. However, suppose you keep it as int **A. Then does *((A+(m * 4) + n)) mean?

Since A is a pointer to a pointer to an int, then A+(m * 4) means to add m * 4 to the pointer. (That is strange spacing, by the way. m and 4 are more tightly bound by the higher-precedence multiplication than A and (m * 4) are by the addition, so why do they have looser spacing? A + m*4 would portray the meaning better.) Then A+(m * 4) + n means to add n to that. In total, we have moved m*4+n elements beyond where A points. Since A points to a pointer, we have advanced the pointer by m*4+n pointers. Then *((A+(m * 4) + n))) dereferences that. When you dereference a pointer to a pointer to an int, you get a pointer to an int. So the result of this expression is a pointer. But you wanted an int.

The link you reference talks about a “2D array”. The kinds of arrays it talks about are implemented using pointers to pointers. To create such an array, you create an array of pointers, and then you set each of those pointers to point to the elements of a row. Then a pointer to that array of pointers acts like a 2D array, in that A[i][j] refers to element j of row i. If you had an array like that, you could refer to element n of row m using A[m][n]. Equivalently, you could refer to it with *(*(A+m)+n). What this expression means is:

  • Take the pointer A and add m to it. Since A points to a pointer to an int, adding m advances the value of the pointer to point to m pointers further along. That is where we should find the pointer to the elements of row m.
  • *(A+m) gets the value of the pointer that A+m points to. This value should be a pointer to the elements of row m, specifically a pointer to the first element (with index 0).
  • *(A+m)+n advances the value of the pointer to point n int further along. That is where we should find element n of row m.
  • *(*(A+m)+n) gets the value of the int that *(A+m)+n points to.

Now suppose instead you changed print to be print(int A[][4], int m, int n). Then your printf statement should use A[m][n], just as before. Or it could use *(*(A+m)+n), also just as before. But, in this case, the expression is evaluated:

  • A is a pointer to an array of 4 int. Adding m to it advances value of the pointer to point m arrays further along.
  • *(A+m) gets the object that A+m points to. This object is an entire array. So this is an expression that designates an array. Following the C rule about arrays in expressions, this array is converted to a pointer to its first element. Thus, *(A+m) becomes a pointer to the first element of the array numbered m.
  • *(A+m)+n advances the value of the pointer to point n int further along. That is where we should find element n of row m.
  • *(*(A+m)+n) gets the value of the int that *(A+m)+n points to.

Thus A[m][n] has the same end result for pointers-to-pointers, for pointers-to-arrays, and for arrays-of-arrays, but the steps it goes through for each are different. C knows the types of each subexpression and processes them differently, to achieve the same result.

Finally, suppose you pass &A[0][0] to print and change its parameter to int *A. Now what is the expression *((A+(m * 4) + n)))? In this case, you are treating the array of 3 arrays of 4 int as one big array of 12 int. Then you calculate where element n of row m is. In this case, A is a pointer to int (not a pointer to a pointer to int). So A+(m * 4) + n is a calculation to where element n of row m ought to be, and *((A+(m * 4) + n))) gets the value of that element.

That is a method you ought to avoid when possible. Generally, you should use C’s built-in methods of addressing array elements and avoid doing your own calculations. Whether it is strictly conforming C code or not may depend on how pedantic you are about interpreting certain passages in the C standard.

Eric Postpischil
  • 141,624
  • 10
  • 138
  • 247
0

If your intention is an array of pointers you cannot initialize it all at once. Your array compiles to the same thing as {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

The following compiles to an array of pointers to arrays:

int arr1[] = {1, 2, 3, 4};
int arr2[] = {5, 6, 7, 8};
int arr3[] = {9, 10, 11, 12};
int* my2DArr[3] = {arr1, arr2, arr3};

It is valid for a nested loop such as this:

for(int i = 0; i < 3; i++)
{
    for(int j = 0; j < 4; j++)
    {
        printf("%d\n", my2DArr[i][j]);
    }
}

For the array in your code, you can iterate over it as if it's just one array.

Chris Rollins
  • 481
  • 3
  • 8