devices
is a large, hardcoded array of structs. It is useful to have an easy way of storing the length of the array that is automatically updated when changes are made to the hardcoded values. my current method (Method A) works for loop iteration, but fails when initializing an array.
Method A:
const uint8_t NUM_DEVICES = (sizeof(devices)/sizeof(DEVICE_T));
LATCHES_T latches[NUM_DEVICES];
is determined to be a VLA and causes variably modified 'latches' at file scope
but method B:
const uint8_t NUM_DEVICES = (sizeof(devices)/sizeof(DEVICE_T));
LATCHES_T latches[(sizeof(devices)/sizeof(DEVICE_T))];
compiles fine. My question is: why does the compiler "know" the size in method B at compile time but not method A? and is there a less stupid way of accomplishing my goal?
Because this is an array of structs, and the size needs to be available to other .c files, a define in the .h file is not an option(sizeof incomplete type).
edit:
Findings
If the size constant is only needed in the corresponding c file, the usual strategy of #define NUM_FOO sizeof(foo)/sizeof(foo_t)
works
i.e: if if devices_arr is defined in devices.c,
#define NUM_DEVICES sizeof(devices_arr)/sizeof(devices_t)
can be safely defined in devices.h, provided that NUM_DEVICES
is only used in devices.c and devices.h
however, if NUM_DEVICES were to be used outside of those two files, the compiler will raise a sizeof incomplete type
error.
The workaround for this that i have used is as folllows:
Method C
//in devices.c
#define _NUM_DEVICES (sizeof(devices)/sizeof(DEVICE_T))
const uint8_t NUM_DEVICES = _NUM_DEVICES;
LATCHES_T latches[_NUM_DEVICES];
this is functionally identical to Method B, but retains most of the readabillity and maintainability of Method A.
feel free to add any better solutions you find
C 2024 6.7.7.3 tells us:
*
(used in function parameter declarations), it is a variable length array.Then we can consider your code:
LATCHES_T latches[(sizeof(devices)/sizeof(DEVICE_T))];
This is not a variable length array because (sizeof(devices)/sizeof(DEVICE_T))
is an integer constant expression. Per 6.6:
An integer constant expression shall have integer type and shall only have operands that are integer constants, named and compound literal constants of integer type, character constants,
sizeof
expressions whose results are integer constants,…1
These requirements are satisfied by (sizeof(devices)/sizeof(DEVICE_T))
because it has integer type and has only operands that are sizeof
expressions whose results are integer constants. (The result of sizeof
is not a constant when its operand has a variable length array type, which is not the case here.)
In contrast, LATCHES_T latches[NUM_DEVICES];
declares a variable-length array because NUM_DEVICES
is not a constant; it is a variable. For the compiler to know the size of the array, it would have to remember what it was told to store in NUM_DEVICES
for its initial value. Historically, the C standard has not required that. It would have required excessive work in early compilers. (The recent addition of constexpr
adds new requirements for compilers.) So this is a variable-length array, not a constant-length array.
If an array with incomplete type is initialized, the initialization will complete it; the size will be determined from the largest indexed element with an explicit initializer. It will then have a constant length and not be a variable length array.
The C standard gives implementations to license to accept more forms of constants than the minimums it requires. So a compiler could accept a variable defined with const
whose initialization is visible to the compiler as a constant, if its designers so chose.
1 There are further details of integer constant expressions not addressed in this answer.
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