0

I coding for "pass dynamic 2D to function" with C.

The following code compiles successfully and runs fine.

void iter_2d(const int** arr, 
             const size_t row, 
             const size_t column) {
// ..some code
}

int main(){
  const size_t column = 3, row = 4;
  int** arr = (int**)malloc(sizeof(int*) * row);
  for (size_t i = 0; i < row; i++)
    arr[i] = (int*)malloc(sizeof(int) * column);
  // init val for array
  iter_2d(arr,row,column);
  // clear array

}

but I get warning:

t.c:24:11: warning: passing argument 1 of 'iter_2d' from incompatible pointer type [-Wincompatible-pointer-types]
   iter_2d(arr,row,column);
           ^~~
t.c:4:26: note: expected 'const int **' but argument is of type 'int **'
 void iter_2d(const int** arr,
              ~~~~~~~~~~~~^~~

I think that function iter_2d just iterate value of array which cannot be modify in function iter_2d,

so input parameter arr should be const to pointer.

But compiler show me this warning made me confused.

curlywei
  • 682
  • 10
  • 18
  • Remove all the `const`s. – S.S. Anne Aug 08 '19 at 19:47
  • 1
    Making pointers to pointers is a bad way to make two-dimensional arrays, as it wastes space and time. If you can target only C implementations that support variable length arrays, then simply do `int (*arr)[column] = malloc(row * sizeof *arr);`, and change the function declaration to `void iter_2d(size_t row, size_t column, const int (*arr)[column])`. If you have to target C implementations that might not support variable length arrays, then you can use a one-dimensional array of `int` and write your own indexing arithmetic (flat subscript from two-dimensional `r` and `c` is `r*column + c`). – Eric Postpischil Aug 08 '19 at 20:12
  • 1
    Also, computer vendors no longer charge for space characters, so you can change `iter_2d(arr,row,column);` to `iter_2d(arr, row, column);` and make other changes for human readability. – Eric Postpischil Aug 08 '19 at 20:13
  • We need a canonical dupe for this question, it is quite common. – Lundin Aug 08 '19 at 20:21
  • And unrelated to your question, you might want to take a look at [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays). – Lundin Aug 08 '19 at 20:22

2 Answers2

8

The reason a conversion from char ** to const char ** violates constraints is given in the C 2018 standard in 6.5.16.1 6, example 3. Suppose we have:

const char **cpp;
char *p;
const char c = 'A';

Next, consider &p. This is a char **. If we allowed a conversion to const char **, then we could assign it to cpp:

cpp = &p; // Violation of C constraints for assignment.

Suppose we did that. Then cpp is a const char **, so *cpp is a const char *. That means we can assign to it the address of a const char, like this:

*cpp = &c;

Now *cpp is a pointer to c. Since cpp points to p, *cpp is p, which means that p points to c. So now we can do this:

*p = 0;

That changes c, but c is a const char, which we are not supposed to be able to change.

So allowing conversions from char ** to const char ** violates the desired behavior for constant objects.

This example uses assignment expressions, but passing arguments to functions is defined to behave like assigning the arguments to the parameters. The constraints are the same.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I don't believe examples are normative text in ISO standards. The constraint would rather be 6.5.16.1/1, third bullet regarding compatible types. – Lundin Aug 08 '19 at 21:19
  • 1
    @Lundin: This answer is written to explain why the rules are the way they are, not to explain how the code in the question violates the rules. – Eric Postpischil Aug 09 '19 at 13:36
1

When passing parameters to a function they are copied "as if by assignment", meaning that the parameter copy follows the same rules as the = operator, formally called simple assignment. So what your code is actually doing between the lines, is essentially the same as this:

int** arr1 = ... ;
const int** arr2 = arr1;

And if you try to compile that snippet, you'll get almost the same error message, saying something like "initialization from incompatible type".


When doing simple assignment, the rule for copying pointers is (simplified):

  • The left operand (of the = operator) can have either qualified or unqualified pointer type. Qualified meaning for example const.
  • Both operands must be pointers to qualified or unqualified versions of a compatible type.
  • The type pointed to by the left must have at least all the qualifiers of the type pointed to by the right. (Meaning int x; const int y = x; is ok but not the other way around.)

For the case where you have int* x; const int* y = x; the compiler won't complain. y is a qualified pointer to int type, x is an unqualified pointer to int type. y has at least all the qualifiers of x. All of the above mentioned rules are fulfilled, so this is fine.

The problem here is how qualifiers behave together with pointer-to-pointer. const int** actually means (read it from right to left) "pointer to pointer to const int". It does not mean "const pointer to pointer to int".

If we go back to the first example of const int** arr2 = arr1;, arr2 is a unqualified pointer to type const int*. And arr1 is an unqualified pointer to type int*. They are not compatible types - it doesn't matter that arr2 happens to point at a type which is a qualified version of what arr1 points at. The rules only care about the "outermost" pointer types themselves.

To fix this and maintain "const correctness", we would have to const qualify the pointer-to-pointer itself. That would be int**const arr. Again, read right to left: "const pointer to pointer to int".


My recommendation would however be to get rid of the pointer to pointer entirely, since your code is needlessly slow because of it. The multiple malloc calls will lead to fragmented allocation and poor data cache utilization. Instead you can use pointer to VLA like this:

#include <stdlib.h>

void iter_2d (size_t row, 
              size_t column,
              int arr[row][column]);

int main (void){
  const size_t row = 4;  
  const size_t column = 3;
  int (*arr)[column] = malloc( sizeof(int[row][column]) );

  iter_2d(row, column, arr);

  free(arr);
}

This code is much faster, less complex, easier to read.
More info: Correctly allocating multi-dimensional arrays.

Lundin
  • 195,001
  • 40
  • 254
  • 396