6

I am writing embedded C code for a microcontroller. The code has to be shared between three different circuit boards, and the input/output configurations are set at run time from some tables during initialization.

The microcontroller has 24 ADC channels, and I have a function that can either set or clear a pin as an ADC channel. This means that an input to the function can consist of 0 to 23 (which is set in the table) and nothing else.

I would like to put some kind of preprocessor or compiler "thing" that could identify that the function received a value other than 0-23 and throw some kind of error or warning and prevent the code from compiling in case someone put an invalid value in the table.

Does anyone have some advice on how best to do this?

Adam
  • 209
  • 1
  • 5
  • 1
    You could use static asserts. – imreal Nov 05 '12 at 18:18
  • 1
    I think you're confusing things here. A function receives input during runtime and preprocessor or compiler can only check stuff that's known during compilation. You should add a check in the beginning of your function that will verify the input boundaries. – SomeWittyUsername Nov 05 '12 at 18:22
  • The reason that I thought there might be some way to check it during compilation is that there will -never- be any values passed to that function which aren't already known at compile time. – Adam Nov 05 '12 at 18:29
  • C doesn't have such capabilities. In C++ this might be achievable with templates. – SomeWittyUsername Nov 05 '12 at 18:35
  • If you know the value before the preprocessor kicks in, (e.g. it is not the result of `sizeof`), then you can use #error if your preprocessor supports that directive. If for some reason the value is not known until the actual compiler starts its work, then it is much trickier. – llakais Nov 05 '12 at 18:44
  • Possible duplicate of [Detecting mismatched array <-> enum initializers](http://stackoverflow.com/questions/10446827/detecting-mismatched-array-enum-initializers), which also gives the proper answer: use static asserts. If you are using a new version of gcc you can also use C standard `static_assert`. – Lundin Nov 06 '12 at 21:05

2 Answers2

3

On most compilers (preprocessors) you can use the #error directive.

I.e.

#define ADC_CHANNEL 34
#if ADC_CHANNEL > 23
#error ADC_CHANNEL exceeds maximum allowed value
#endif

The above would throw an error, and would not compile.

Then use ADC_CHANNEL as the input to your function.

Or you can make ADC_CHANNEL an enum, and define ADC_CHANNEL_0 = 0, ADC_CHANNEL_1 = 1 ... ADC_CHANNEL_23 = 23. Then make your function take type ADC_CHANNEL_t, or whatever you want to call it, and that way if the function is called using the enumerated type as the argument, there will be no way to use an out-of-bounds value.

Example:

typedef enum {ADC_CHANNEL_0 = 0,
              ADC_CHANNEL_1 = 1,
              ADC_CHANNEL_2 = 2,
              // ...etc...
              ADC_CHANNEL_22 = 22,
              ADC_CHANNEL_23 = 23} adc_channel_t;

void setClearAdcPin(adc_channel_t adcChannel) {
    // ...function body...
}

(You don't technically need the = 0, = 1, etc., since the compiler will infer that from the order. By default enums start at 0 and increment by 1 for each value. But defining each value manually is safer, and lets you do things like only include the 3 possible ADC channels that you might possibly use, even if they aren't consecutive.)

llakais
  • 1,577
  • 1
  • 12
  • 18
  • 1
    Unfortunately, standard integer will be accepted even if the func expects an enum: http://liveworkspace.org/code/69da85aa7cfefb69d7d6f23b940f7462 – SomeWittyUsername Nov 05 '12 at 18:34
  • True, that will not throw a compiler error. I guess I just meant that if you make it clear that the function is to be called with the enumerated type, then hopefully someone would be less likely to screw it up. But one shouldn't underestimate people's ability to screw things up. – llakais Nov 05 '12 at 18:38
  • Use some compiler options to making the type checking throw an error. – Conor OG Nov 08 '12 at 00:52
1

You can check the range in compiler (as opposed to the preprocessor) by this dirty trick:

const char PIN = 23;
struct check_23 {
    unsigned long bits: PIN+9;
};

this won't compile if PIN > 23

Then, to ensure your function is given only compile-time constants as arguments you supply a macro instead of a function:

#define CONCAT(a, b) a##b
#define MAKESTRUCTNAME(a,b) CONCAT(a,b)

#define PinFunction(PinArg)\
struct MAKESTRUCTNAME(PinCheckStruct, __LINE__) {\
    unsigned long bits: (PinArg)+9;\
};\
RealPinFunction(PinArg);\
panda-34
  • 4,089
  • 20
  • 25
  • How is this related to the function call? – SomeWittyUsername Nov 05 '12 at 18:36
  • @icepack, you prepend each function call with it. (Didn't have time to write it all back then) – panda-34 Nov 05 '12 at 19:18
  • Nice. You'll also need to deny the user the real function interface to prevent abuse. Other than that seems like a heroic attempt to emulate some of C++ template capabilities :) – SomeWittyUsername Nov 05 '12 at 19:25
  • This is not a good idea. Bit-fields are notoriously poorly defined, _particularly_ on microcontroller applications. Also, I believe using any other integer type than int for a bit-field is undefined behavior in any version of the C standard. – Lundin Nov 06 '12 at 21:04