1

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 {
    int pin;   
    int channel;
};

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

Struct def

const struct adc_cfg adc1 = {
    .pin_count = 2,  // I'd like to determine this programmatically
    .pins = {
        {4, 5},
        {6, 7},
    },
};

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

4
  • 1
    This is not possible. You cannot create a variable with such a type. You must use dynamic memory allocation and calculated required size on your own. Commented Dec 17, 2021 at 21:15
  • 1
    In the future, prepare a minimal reproducible example. Your sample code does not provide definitions for GPIO_TypeDef, so other people cannot readily compile it. The specific types are not needed for this question; you could easily replace the types of the members of adc_pin with int, as I have done in my answer. Commented Dec 17, 2021 at 21:30
  • Fair enough. I like to have code questions which are easy to read for future use, and abstract a b c have less meaning for many of us than physical quantities, even if it isn't directly copy-pasta'able. However, I see your point as well and as you're the one doing me the favor of trying to answer my question I figure I can certainly defer to you on this. Commented Dec 17, 2021 at 23:07
  • @Gerhardh I don't understand your comment. We've been using this code for almost a decade, so your comment seemingly contradicts our lived experience with GCC. Commented Dec 18, 2021 at 15:52

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This requires the precompiler, which is unfortunately what I want to avoid. The consequence of this is code configuration which is spread out across multiple chunks, and done in multiple styles. That transforms one type of risk into another, instead of eliminating the risk.
1

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[];
};

#define PINS_CFG {{0,1}, {2,3}}
#define PINS_COUNT(PIN_CFG) sizeof (struct adc_pin[]) PINS_CFG / sizeof (struct adc_pin)

const union adc_cfg_ {
    struct adc_cfg cfg;
    struct {
        int pin_count;
        struct adc_pin pins[PINS_COUNT(PINS_CFG)];
    };
} adc_cfg_ = {
    .cfg.pin_count = PINS_COUNT(PINS_CFG),
    .pins = PINS_CFG,
};

_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.

The size of the pins array is computed using a compound literal. For example an initializer {1,2,3} can be transformed into an anonymous array with expression (int[]){1,2,3}. This array can be used with a traditional method of deriving the count of array as sizeof <array> / sizeof array-element.


EDIT.

6 Comments

I like where this is going, and it's teaching me good stuff about C. What I don't see, though, is why it would make sense to use the complexity of calculating the number of elements since it's already explicitly defined by struct adc_pin pins[2];. It would be more straightforward to #define NUM_ELEMS 2 and then use that to set .pin_count.
@KennSebesta, indeed, I've somehow missed it. See the updated answer. It's the same method as in Eric's answer
@KennSebesta "It would be more straightforward to #define NUM_ELEMS 2 and then use that to set .pin_count", yes, but more error prone than calculating the "2" from PINS_COUNT. Do not look to what is more straightforward base on writing the code. Instead look to what is best based on maintaining the code. Code maintenance is more expensive and error prone than initial writing.
@chux-ReinstateMonica errorprone from which perspective? All these abstractions make some parts easier, and other other parts harder. The right answer might be impossible with C, which is to be able to use simple C to do all this instead of relying on the precompiler to tell us what the compiler itself knows-- how big the memory will be when this const array is initialized.
@KennSebesta To answer the question of your comment: less error prone from the perspective that deriving the count from the PINS_CFG has the extensibility of adding new knowledge variables in one place than manually maintaining two parallel definitions NUM_ELEMS in sync with PINS_CFG.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.