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
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With