18

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.

10
  • 1
    I guess the point is to design everything with const correctness in mind? Commented Mar 24, 2016 at 11:54
  • The correct pointer to pass is one dimension less that the array you pass. int *arr for int ia[3]. That way you just pass the array itself. Or, being symmetric - just use the same declaration for argument like for the passed array: int arr[] and use the implicit conversion of the formal parameter. Commented Mar 24, 2016 at 11:55
  • 1
    @Olaf No then you would get array decay and the whole increased type safety argument is lost. A parameter int param[n] has no increased type safety compared to int* param. Commented Mar 24, 2016 at 11:59
  • @Lundin: Ok, so you accept the added * for each array access. Got it. Commented Mar 24, 2016 at 12:04
  • 1
    That's why I personally think MISRA is a good reading, but mostly because of the rationals. It is always bad to treat style guides like a religion. If it comes to compliance vs. quality, I'd always prefer the latter. (I'm very well aware we don't always have the choice) Commented Mar 24, 2016 at 12:17

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

5 Comments

The VLA part is not really needed, it was just the example I had. The same problem exists with plain static-sized arrays.
@Lundin Yes. One time I used a setup typedef char KEY_BUFFER[8]; typedef char const CKEY_BUFFER[8]; , and if calling a function that accepted a const buffer, write code like: KEY_BUFFER buf; ... check_buf( (CKEY_BUFFER *)&buf ); , however looking at the end result the fact that you have to use the cast means that there is little gain in type safety, and a cost in readability . I ended up just not using the const version and relying on documentation. I probably wouldn't try that setup again if writing from scratch.
@M.M I had the idea of using a macro #define const_cast(n, arr) _Generic(arr, int(*)[n] : (const int(*)[n])arr ). And then force the caller to use that macro, sort of the same idea as your example, except this should be type safe as far as I can tell. Still, it adds clutter to the caller code.
Given the parameter declaration int n, int (*arr)[n], it is not undefined behaviour to pass an array smaller than n - you need to use int n, int (*arr)[static n]. Without the static, the n is ignored completely and passing a smaller array is well-defined (not great IMO but there we are). Of course MISRA forbids the use of static (because the MISRA authors don't understand what it is for and would prefer less-safe code, R17.6).
@Leushenko C11 6.7.6.2/6 says that it IS undefined behaviour if the argument corresponding to int (*arr)[n] is not precisely the address of an array of type int[n]. In int (*arr)[static n], the static has no effect. You are mixing up this case with a parameter declaration of int v[n] versus int v[static n], (for which your analysis would be correct).
5

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++

3 Comments

Umm yeah that's what I explained in the question? "But then I can no longer pass the array, because of incompatible pointer types." This is the problem, not the solution. Or are you saying that there is no solution? I'm only interested in standard C.
@Lundin; Sorry to say there is no solution as of now. But, you can use GCC extension.
gcc vs. C - 1:0 . @Lundin: When it comes to const-correctness, C has quite a lot of pitfalls. IIRC there was a question about nested pointers with mixed const/not const which cannot be detected correctly. Your's is mostly because of the broken grammar, thus the gcc extension will very likely not make it into the standard. You actually would have to appliy const to int [3] (which would be one reason some derived languages use this).
4

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);

3 Comments

This is roughly the same trick as I came up with, see comments. Not sure if is such a good idea to use such a macro though... you add lots of complexity, probably doing more harm than good. You'd have to document the macro anyway and say "in order to call func, call funcCC". So you might as well document the original function that lacks const correctness and say "hey, I promise I won't touch the contents passed".
@Lundin Amongst many programming tasks I've done, I would not rate this as "lots of complexity". To best rate (over) complexity, better to see more of the overall picture and competing choices. This does maintain type safety, which was a stated goal. Complexity was not a posted goal, yet this does simplify calling the function. Certainly better than the func(3, &array);. Anyways - fun question - probing new corners of C.
@Lundin BTW: Such a highly rated and interesting question deserve more time before accepting an answer. I am confident other thought answers would more likely appear if unaccented for longer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.