Const correctness for array pointers?

Someone made an argument saying that in modern C, we should always pass arrays to functions through an array pointer, since array pointers have strong typing. Example:

void func (size_t n, int (*arr)[n]);
...

int array [3];
func(3, &array);

This sounded like it could potentially be a good idea to prevent all kinds of type-related and array-out-of-bounds bugs. But then it occurred to me I don't know how to apply const correctness to this.

If I do void func (size_t n, const int (*arr)[n]) then it is const correct. But then I can no longer pass the array, because of incompatible pointer types. int (*)[3] versus const int (*)[3]. The qualifier belongs to the pointed-at data and not to the pointer itself.

An explicit cast in the caller would ruin the whole idea of increased type safety.

How do I apply const correctness to array pointers passed as parameters? Is it at all possible?


EDIT

Just as info, someone said that the idea of passing arrays by pointer like this probably originates from MISRA C++:2008 5-2-12. See for example PRQA's high integrity C++ standard.


Solution 1:

There is no way to do it except for the cast. This is significant drawback of the idea to pass arrays in this way.

Here is a similar thread where the C rules are compared to the C++ rules. We could conclude from this comparison that the C rules are not so well designed, because your use case is valid but C doesn't allow the implicit conversion. Another such example is conversion of T ** to T const * const *; this is safe but is not allowed by C.

Note that since n is not a constant expression, then int n, int (*arr)[n] does not have any added type safety compared to int n, int *arr. You still know the length (n), and it is still silent undefined behaviour to access out of bounds, and silent undefined behaviour to pass an array that is not actually length n.

This technique has more value in the case of passing non-VLA arrays , when the compiler must report if you pass a pointer to an array of the wrong length.

Solution 2:

C standard says that (section: §6.7.3/9):

If the specification of an array type includes any type qualifiers, the element type is so- qualified, not the array type.[...]

Therefore, in case of const int (*arr)[n], const is applied to the elements of the array instead of array arr itself. arr is of type pointer to array[n] of const int while you are passing a parameter of type pointer to array[n] of int. Both types are incompatible.

How do I apply const correctness to array pointers passed as parameters? Is it at all possible?

It's not possible. There is no way to do this in standard C without using explicit cast.

But, GCC allow this as an extension:

In GNU C, pointers to arrays with qualifiers work similar to pointers to other qualified types. For example, a value of type int (*)[5] can be used to initialize a variable of type const int (*)[5]. These types are incompatible in ISO C because the const qualifier is formally attached to the element type of the array and not the array itself.

 extern void
 transpose (int N, int M, double out[M][N], const double in[N][M]);
 double x[3][2];
 double y[2][3];
 ...
 transpose(3, 2, y, x); 

Further reading: Pointer to array with const qualifier in C & C++

Solution 3:

OP describes a function func() that has the following signature.

void func(size_t n, const int (*arr)[n])

OP wants to call it passing various arrays

#define SZ(a) (sizeof(a)/sizeof(a[0]))

int array1[3];
func(SZ(array1), &array1);  // problem

const int array2[3] = {1, 2, 3};
func(SZ(array2), &array2);

How do I apply const correctness to array pointers passed as parameters?

With C11, use _Generic to do the casting as needed. The cast only occurs when the input is of the acceptable non-const type, thus maintaining type safety. This is "how" to do it. OP may consider it "bloated" as it is akin to this. This approach simplifies the macro/function call to only 1 parameter.

void func(size_t n, const int (*arr)[n]) {
  printf("sz:%zu (*arr)[0]:%d\n", n, (*arr)[0]);
}

#define funcCC(x) func(sizeof(*x)/sizeof((*x)[0]), \
  _Generic(x, \
  const int(*)[sizeof(*x)/sizeof((*x)[0])] : x, \
        int(*)[sizeof(*x)/sizeof((*x)[0])] : (const int(*)[sizeof(*x)/sizeof((*x)[0])])x \
  ))

int main(void) {
  #define SZ(a) (sizeof(a)/sizeof(a[0]))

  int array1[3];
  array1[0] = 42;
  // func(SZ(array1), &array1);

  const int array2[4] = {1, 2, 3, 4};
  func(SZ(array2), &array2);

  // Notice only 1 parameter to the macro/function call
  funcCC(&array1);  
  funcCC(&array2);

  return 0;
}

Output

sz:4 (*arr)[0]:1
sz:3 (*arr)[0]:42
sz:4 (*arr)[0]:1

Alternatively code could use

#define funcCC2(x) func(sizeof(x)/sizeof((x)[0]), \
    _Generic(&x, \
    const int(*)[sizeof(x)/sizeof((x)[0])] : &x, \
          int(*)[sizeof(x)/sizeof((x)[0])] : (const int(*)[sizeof(x)/sizeof((x)[0])])&x \
    ))

funcCC2(array1);
funcCC2(array2);