The variable data
is a variable length array, which do not necessarily need to have a integer constant size when declared as a function parameter:
If expression is not an integer constant expression, the declarator is for an array of variable size.
In fact, the size is implicitly ignored at function prototype scope:
If the size is *, the declaration is for a VLA of unspecified size. Such declaration may only appear in a function prototype scope, and declares an array of a complete type. In fact, all VLA declarators in function prototype scope are treated as if expression were replaced by *.
("Expression" is what is usually between the square brackets and specifies the size.)
In other words, at a function prototype, the size does not need to be known at all and still the type is considered to be complete. Only in the function definition does the size need to be specified as some non-constant integer expression. In the call to the function, the integer expression may be const or non-const I would imagine this (effectively) works similarly to when VLAs are declared and defined in a loop, i.e.:
Each time the flow of control passes over the declaration, expression is evaluated (and it must always evaluate to a value greater than zero), and the array is allocated (correspondingly, lifetime of a VLA ends when the declaration goes out of scope).
(Emphasis mine.)
...except that the size is now evaluated at the time of the function call.
In your particular case, you basically have a variably-modified type:
Variable-length arrays and the types derived from them (pointers to them, etc) are commonly known as "variably-modified types" (VM). Objects of any variably-modified type may only be declared at block scope or function prototype scope.
And yes, the code does compile. An example:
#include <inttypes.h>
#include <stdio.h>
struct data_el
{
int n;
};
// Function prototype. Not specifying a size is allowed
void cistore(uint32_t bucketsize, struct data_el data[][*]);
// Function definition.
// Must specify the VLA's size as a non-constant integer expression
void cistore(uint32_t bucketsize, struct data_el data[][bucketsize])
{
(*data)[1].n = 5;
printf("%d\n", (*data)[1].n);
}
int main(void)
{
// Function call. Use any (const or non-const) integer expression.
uint32_t bucketsize = 2U;
struct data_el data[bucketsize];
cistore(bucketsize, &data);
}
Interestingly, Clang compiles this without warnings, whereas GCC warns about type conflicts:
vlaquestion.c:14:50: warning: argument 2 of type ‘struct data_el[][bucketsize]’ declared as a variable length array [-Wvla-parameter]
14 | void cistore(uint32_t bucketsize, struct data_el data[][bucketsize])
| ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
vlaquestion.c:10:50: note: previously declared as an ordinary array ‘struct data_el[][0]’
10 | void cistore(uint32_t bucketsize, struct data_el data[][*]);
| ~~~~~~~~~~~~~~~^~~~~~~~~
And finally, some useful reading on the somewhat controversial VLAs: Why aren't variable-length arrays part of the C++ standard?
At some point, even an effort was made to make the Linux kernel VLA free.