Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

const / static / volatile in array type specification?

Tags:

c

c11

What does the following function signature define in C?!

#include <stdio.h>
#include <string.h>

void f(int a[const volatile static 2])
{
    (void)a;
}

int main() {
    int b[1];
    f(b);
}

https://godbolt.org/z/6qPxaM1vM

I don't get the meaning of const / volatile / static at this place, but it seems to compile, so I guess it has a meaning?

Thanks

like image 711
Kevin Meier Avatar asked Oct 14 '21 14:10

Kevin Meier


2 Answers

This is a mildly useful feature introduced in C99. From C17 6.7.6.3/7:

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.

Meaning in this case the qualifiers const and volatile means that the array decays into a pointer of type int* const volatile a. Since this qualified type is local to the function, it means very little to the caller, since a passed pointer is assigned by lvalue conversion and can still be a non-qualified pointer type.

The static is ever so slightly more useful, as it supposedly enables some compile-time size checks by the compiler, though in practice it seems that the mainstream compilers (currently) only manage to check that the pointer is not null. For example f(0) on clang gives:

warning: null passed to a callee that requires a non-null argument [-Wnonnull]

Curiously, f(0) on gcc 11.1 and beyond says:

warning: argument 1 to 'int[static 8]' is null where non-null expected [-Wnonnull]"

No idea what the 8 is coming from, I guess it's a typo/minor compiler bug (the decayed pointer is 8 bytes large).

like image 130
Lundin Avatar answered Oct 18 '22 20:10

Lundin


A parameter of array type is automatically adjusted to a pointer type. Therefore a parameter declared as:

int A[3]

is transformed to:

int *A

However, with the array notation there is no intuitive place to add qualifier the variable a itself (not the data pointed by a). Therefore C standard allows to put those specifier between brackets.

Thus:

void f(int a[const volatile restrict 2])

is actually:

void (int * const volatile restrict a)

The size (2 in above example) is usually ignored. The exception is when static keyword is used. It provides a hint for a compiler that at least elements at addresses a + 0 to a + size - 1 are valid. This hint in theory should improve optimization by simplifying vectorization. However, AFAIK it is ignored by major compilers.

Your code:

int b[1];
f(b);

is triggering UB because only element at address b + 0 is valid while the hint requires b+0 and b+1 to be valid. Better compilers/sanitizers should detect that.

The static is also useful for self documentation and detection of errors like telling that at least n elements pointed by a pointer must be valid:

void fun(int n, int arr[static n])

or even telling that the pointer is never NULL:

void fun(int ptr[static 1]);

Moreover syntax int buf[static n] is a good visual hint that something is actually not an array. It help to avoid a common error when trying to acquire the a size of "array" with sizeof buf syntax.


EDIT

As stated on the comment the word "hint" may be a bit misleading because it could be interpreted that violation the "hint" is not an error though it may result in some non-optimality (like performance degradation). Actually, it is rather a requirement, violating which results in undefined behavior.

like image 2
tstanisl Avatar answered Oct 18 '22 20:10

tstanisl