Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 765
klutt Avatar asked Aug 26 '21 10:08

klutt


People also ask

Why can't you change the size of an array?

If you create an array by initializing its values directly, the size will be the number of elements in it. Thus the size of the array is determined at the time of its creation or, initialization once it is done you cannot change the size of the array.

How do you pass the size of an array to a function?

The first one would be called like func1(foo, foo + CAPACITY) : passing in a pointer to the start of the array, and a pointer to just beyond the last element. The second would be called like func2(foo, CAPACITY) : passing in a pointer to the start of the array and the array size.

How can you change the size of array after it has been created?

Once an array has been created, its size cannot be changed. Instead, an array can only be "resized" by creating a new array with the appropriate size and copying the elements from the existing array to the new one.

Can we change the size of array after declaration?

Arrays can either hold primitive values or object values. An ArrayList can only hold object values. You must decide the size of the array when it is constructed. You can't change the size of the array after it's constructed.


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?

like image 167
Lundin Avatar answered Sep 28 '22 06:09

Lundin


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.

like image 32
dbush Avatar answered Sep 28 '22 06:09

dbush