Home > Mobile >  How to get the size of an array nested in a const struct declaration
How to get the size of an array nested in a const struct declaration

Time:12-18

I have perhaps a novel take on the classic "In C, how can I determine the number of elements in an array". I'd like to programmatically determine the number of elements in an array which is a field in a const struct, and I'd like to store that number in the self-same struct.

Embedded struct defs

struct adc_pin {
    GPIO_TypeDef *port;
    uint32_t pin;   
    uint8_t channel;
};

struct adc_cfg {
    uint8_t pin_count;  /**< Number of ADC pins to use */
    struct adc_pin pins[];  /**< Array of pins to use */
};

Struct def

const struct adc_cfg adc1 = {
    .pin_count = 2,  // I'd like to determine this programmatically
    .pins = {
        {GPIOA, GPIO_Pin_0, ADC_Channel_1},
        {GPIOA, GPIO_Pin_1, ADC_Channel_2},
    },
};

Is this possible with C, without resorting to an exotic pre-compiler macro?

P.S. Failing some way to programmatically determine this, it would be good to have a compile-time failure which could catch it, but that seems harder than it should be since static asserts in C specifically are designed to fail on const declared variables: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102502

CodePudding user response:

You can do it thusly:

#include <stdint.h>

//  Note:  Changed member types for simplicity of example.
struct adc_pin { int a, b, c; };

struct adc_cfg {
    uint8_t pin_count;  /**< Number of ADC pins to use */
    struct adc_pin pins[];  /**< Array of pins to use */
};

//  List initializers for array of struct adc_pin.
#define MyPins  \
    { 0, 1, 2 }, \
    { 3, 4, 5 },

const struct adc_cfg adc1 = {

        //  Use compound literal to calculate number of elements in array.
        .pin_count = sizeof (struct adc_pin []) { MyPins } / sizeof (struct adc_pin),

        //  Initialize flexible array member from prepared list.
        .pins = { MyPins },

    };

Note that initializing a flexible array member is a GNU (and Clang) extension to the C standard.

CodePudding user response:

It could be well approximated in standard C by embedding struct adc_cfg in a a union where the other member will be the an array of struct adc_pin of a fixed size.

#include <stdio.h>
#include <stddef.h>

struct adc_pin {
    int pin;   
    int channel;
};

struct adc_cfg {
    int pin_count;
    struct adc_pin pins[];
};

const union adc_cfg_ {
    struct adc_cfg cfg;
    struct {
        int pin_count;
        struct adc_pin pins[2];
    };
} adc_cfg_ = {
    .cfg.pin_count = sizeof adc_cfg_.pins / sizeof adc_cfg_.pins[0],
    .pins = { {0,1}, {2,3} },
};

_Static_assert(offsetof(union adc_cfg_, cfg.pins) == offsetof(union adc_cfg_, pins), "oops");

#define adc1 (adc_cfg_.cfg)

int main() {
    printf("%d %d\n", adc1.pins[0].pin, adc1.pins[0].channel);
    printf("%d %d\n", adc1.pins[1].pin, adc1.pins[1].channel);
}

The program prints the expected output of

0 1
2 3

The trick is that the arrays adc_cfg_.cfg.pins and adc_cfg_.pins alias because they are placed in the same union at the same position.

From definition of flexible array member 6.7.2.1p18:

it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed

The object is the whole union so the flexible member should behave like an array of two elements.

Theoretically, this solution is not-full portable because the offset of pins in the anonymous union does not have to the same as offset within the object. However, it is very unlikely and it can be easily checked with _Static_assert as in the code snippet.

Moreover the rule 6.5.2.3p6 about the common initial sequence suggests that the code will work in all cases and be portable.

  • Related