Saturday 29 October 2016

Constructing Structures at Compile-time in C

In embedded software, it's common to need to pass board-specific configuration data to a function.

In such cases, it's tempting to declare a set of const's (typically structures) to hold all the configuration values .

The downside of this approach is that memory is precious in embedded; d
efining lots of constants, not all of which may be used, is undesirable.

To give a concrete example


Suppose we're writing a library function to start-up a microprocessor's analog-to-digital converter:


fStartADC( pConfigData );

Here pConfigData is a pointer to a C-Structure containing configuration data for the ADC (for example the pin on the board you want the ADC to connect to).

If there are seven (say) possible ADC configurations, its tempting to declare a set of const. structures CONFIG0 ... CONFIG6, for users of your library function.

Should a user only make use of (say) CONFIG3 however, you risk having six unused structures bloating memory.

Instead, you'd like to provide users of your library a set of predefined structures CONFIG0 ... CONFIG6, but have the compiler only create (instantiate) those that the user actually uses.

C++ offers you such things as constructors to help with this. In C it takes a little more work, but here's one way:

To keep things simple, suppose our ADC CONFIG structure needs to hold a single integer, x (in real life, configuring the ADC need require multiple e.g. pin, port, oscillator... etc. values)

First, we declare a type for our structure and a pointer to it.

typedef struct { int x; } ConfigData_t, *pConfigData_t

Next, we define a function that takes an integer, creates a structure containing the integer, and returns a pointer to it.

pConfigStruc_t fConstructor( int x ) {
static ConfigStruc_t CONFIGn;
CONFIGn.x = x;
return &CONFIGn; }


Finally, we define some macro's

#define CONFIG0 ( fConstructor ( 7) )
#define CONFIG1 ( fConstructor (67) )
..etc.


where 7, 67 are examples of board-specific configuration values.

We can now offer users of our library, a set of predefined configurations CONFIG0,CONFIG1 insulating (abstracting) them from any messy details of our board hardware. The user is free to write a line of code like:

fStartADC( CONFIG0 );


At compile-time, in accordance with our #define, the compiler will dutifully replace CONFIG0 with fConstructor(7)

fConstructor (7) will then do its job, returning a pointer to a structure containing '7'.

Notice however, that since our user never wrote CONFIG1, a structure holding 67 was never created!

We've met our goal of providing users with a set of predefined CONIG's  but at the same time made sure that only those they use will be compiled into memory. Neat huh!

To see a 'real world' example of this technique, check-out my gitHub code here

No comments:

Post a Comment