How do I force a warning from using an array of wrong size when passed to function?

Viewed 132

Let's say you have a function taking a string as an argument:

void foo(char *arg);

If we know for certain that the array (not to be confused with string length, thanks chux) will always have a certain size, let's say 8, then we can instead do:

void bar(char (*arg)[8]);

and then call it like this:

char str[8] = "Hello";
bar(&str);

We need to add the & for this to work properly, but the above code will emit a warning if you pass an array of wrong size or type, which is exactly what I want to achieve. But we will obviously need to modify the body a bit. So my question is simply if this wrapper technique would work:

void bar(char (*arg)[8]) {
    char *tmp = (char*) arg;
    foo(tmp);
}

What I'm trying to achieve here is that warnings should be emitted if called with an array of wrong size. Is the above solution safe? Is it safe to cast pointer to array of char to pointer to char? I tried it, and it works, and emits no warnings with -Wall -Wextra -pedantic. And as soon as I change the size of str I get:

<source>: In function 'main':
<source>:18:9: warning: passing argument 1 of 'bar' from incompatible pointer type [-Wincompatible-pointer-types]
   18 |     bar(&str);
      |         ^~~~
      |         |
      |         char (*)[9]
<source>:9:17: note: expected 'char (*)[8]' but argument is of type 'char (*)[9]'
    9 | void bar(char (*arg)[8]) {
      |          ~~~~~~~^~~~~~~

which is exactly what I want. But is it safe, or is it UB? I would like to do this, not only via a wrapper, but also by rewriting the original function, like

void foo(char (*argaux)[8]) {
    char *arg = *argaux;
    // Copy body of original foo

I know that I can achieve basically the same thing using structs, but I wanted to avoid that.

Runnable code: https://godbolt.org/z/GnaP5ceMr

2 Answers

char *tmp = (char*) arg; is wrong, these are not compatible pointer types. You can fix this easily though:

char *tmp = *arg;

*arg gives a char[8] which then decays into a pointer to its first element. This is safe and well-defined. And yes, pointers have much stronger "typing" in C than pass-by-value, so the compiler will recognize if an array of wrong size is passed.

Please note however that this leads to other problems: you can no longer have const correctness.
See Const correctness for array pointers?

This is not safe:

char *tmp = (char*) arg;

Because you're attempting to convert a char (*)[8] to a char *. While you might get away with it since a pointer to an array will (at least on x86-64) have the same numeric value as a pointer to the first member of an array, the standard doesn't guarantee that it will work. You would first need to dereference the parameter:

char *tmp = *arg;

In theory you should be able to do this:

void foo(char arg[static 8]);

This means that arg must be an array of at least that size.

The description of this syntax is in section 6.7.6.3p7 of the C standard:

A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

However, most implementations don't enforce this restriction and it doesn't prevent you from passing an array larger than expected.

Related