18 comments

[ 5.3 ms ] story [ 51.7 ms ] thread
Funny thing about that n[static M] array checking syntax–it was even considered bad in 1999, when it was included:

"There was a unanimous vote that the feature is ugly, and a good consensus that its incorporation into the standard at the 11th hour was an unfortunate decision." - Raymond Mak (Canada C Working Group), https://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_205.htm

Better option: just wrap it in a unique struct.

There are perhaps only 3 numbers: 0, 1, and lots. A fair argument might be made that 2 also exists, but for anything higher, you need to think about your abstraction.

Pointer to array is not only type-safe, it is also objectively correct and should have always been the syntax used when passing in the address of a known, fixed size array. This is all a artifact of C automatically decaying arrays to pointers in argument lists when a array argument should have always meant passing a array by value; then this syntax would have been the only way to pass in the address of a array and we would not have these warts. Automatic decaying is truly one of the worst actual design mistakes of the language (i.e. a error even when it was designed, not the failure to adopt new innovations).
And the reason why C has array-pointer decay is because that made it work more or less like B (which had to do it since it literally didn't have any type other than machine word).
GCC also has an extension to support references to other parameters of the function:

    #include <stddef.h>
    void foo(size_t n, int b[static n]);
https://godbolt.org/z/c4o7hGaG1

It is not limited to compile-time constants. Doesn't work in clang, sadly.

That weird feeling when you realise that the people you hang out with form such a weird niche that something considered common knowledge among you is being described as "buried deep within the C standard".

What's noteworthy is that the compiler isn't required to generate a warning if the array is too small. That's just GCC being generous with its help. The official stance is that it's simply undefined behaviour to pass a pointer to an object which is too small (yes, only to pass, even if you don't access it).

The pointer-to-array solution is okay, with the caveat that pointer-to-array typedefs should be avoided.

The problem is that they are attractive for reducing repeated declarations:

  typedef unsigned char thing_t[THING_SIZE];

  struct red_box_with_a_hook {
     thing_t thing1, thing2;
  }

  void shake_hands_with(thing_t *thing);
That is all well. But thing_t is an array type which still decays to pointer.

It looks as if thing_t can be passed by value, but since it is an array, it sneakily isn't passed by value:

  void catch_with_net(thing_t thing);  // thing's type is actually "usnsigned char *"

  // ...
    unsigned char x[42]];
    catch_with_net(x);        // pointer to first element passed; type checks
Indeed, I think one should avoid such typedefs. You could wrap it in a struct.
yet if one does that, and has suitable warnings turned on, GCC will complain:

    array2.c: In function ‘main’:
    array2.c:25:17: warning: passing argument 1 of ‘arr_fn2’ from incompatible pointer type [-Wincompatible-pointer-types]
       25 |         arr_fn2(array);
          |                 ^~~~~
          |                 |
          |                 char *
    array2.c:13:22: note: expected ‘char (*)[15]’ but argument is of type ‘char *’
       13 | void arr_fn2(Arr_15 *arr) {
          |              ~~~~~~~~^~~

The above was -Wall -Wextra

Or are you just referring to the function where one defines it as apparently 'pass by value'?

Anyone know if there's a flag to tell clang to treat `void fn(int array[N])` as if it was `void fn(int array[static N])`?
I don't think such a thing exists simply because it would break so many headers if you turned that on, it wouldn't be particularly useful.
Why would it break headers? Who writes fn(array[16]) expecting less that 16 elements?
They might expect 16 elements if it's not null, but still consider null a valid value, for example.
But only for constant size arrays.

You could just declare

    struct Nonce {
        char nonce_data[SIZE_OF_NONCE];
    }
and pass those around to get roughly the same effect.
Unfortunately you cannot use static in array typedefs, which really blows. So you have to have an extra constant to keep track of the array size in order to use it. If it worked on typedefs, you could just make the array parameter the appropriate type and derive the array's count with sizeof(array_type_t).