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.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    I guess the point is to design everything with const correctness in mind? – George Stocker Mar 24 '16 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. – too honest for this site Mar 24 '16 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`. – Lundin Mar 24 '16 at 11:59
  • @Lundin: Ok, so you accept the added `*` for each array access. Got it. – too honest for this site Mar 24 '16 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) – too honest for this site Mar 24 '16 at 12:17
  • I suppose you could invent something like `#define const_cast(n, arr) _Generic(arr, int(*)[n] : (const int(*)[n])arr ) ` and then call the function as `func(3, const_cast(3, &array));`. But that's rather bloated. – Lundin Mar 24 '16 at 12:31
  • I wonder when you call `func(3, &array)`, is the whole array containing 3 elements copied and passed? – nalzok Mar 24 '16 at 12:56
  • @sunqingyao No of course not. You can't pass arrays by value in C, unless you wrap them inside a struct (bad idea). – Lundin Mar 24 '16 at 12:57
  • @Lundin Well...And is naming a type with a non-constant expression (I mean `int (*)[n]`) allowed in C? Although my compiler doesn't give me any warning about it, this seems quite strange to me – nalzok Mar 24 '16 at 13:01
  • @Lundin Also, I notice that to access the `i`th element of `arr[]`in `func()`, you have to use `(*arr)[n]`, which may make your code less readable. – nalzok Mar 24 '16 at 13:13

3 Answers3

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

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
  • 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 Mar 24 '16 at 12:02
  • @Lundin; Sorry to say there is no solution as of now. But, you can use GCC extension. – haccks Mar 24 '16 at 12:03
  • 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). – too honest for this site Mar 24 '16 at 12:11
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.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • 1
    The VLA part is not really needed, it was just the example I had. The same problem exists with plain static-sized arrays. – Lundin Mar 24 '16 at 12:34
  • @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 Mar 24 '16 at 12:39
  • @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. – Lundin Mar 24 '16 at 12:55
  • 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](https://softwareengineering.stackexchange.com/q/331757/98794) 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). – Alex Celeste Aug 03 '17 at 11:58
  • 2
    @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). – M.M Aug 03 '17 at 12:37
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);
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 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 Mar 24 '16 at 14:57
  • @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. – chux - Reinstate Monica Mar 24 '16 at 15:26
  • @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. – chux - Reinstate Monica Mar 24 '16 at 15:28