Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What determines when an array is considered a variable length array

Tags:

arrays

c

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

like image 963
Elliot White Avatar asked Sep 07 '25 15:09

Elliot White


1 Answers

C 2024 6.7.7.3 tells us:

  • If the size is not present, the array type is incomplete.
  • If the size is * (used in function parameter declarations), it is a variable length array.
  • If the size is an integer constant expression, it is not a variable length array.
  • Otherwise, 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.

Notes

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.

Footnote

1 There are further details of integer constant expressions not addressed in this answer.

like image 200
Eric Postpischil Avatar answered Sep 10 '25 12:09

Eric Postpischil