Ways to ASSERT expressions at build time in C - c

I'm tidying up some older code that uses 'magic numbers' all over the place to set hardware registers, and I would like to use constants instead of these numbers to make the code somewhat more expressive (in fact they will map to the names/values used to document the registers).
However, I'm concerned that with the volume of changes I might break the magic numbers. Here is a simplified example (the register set is more complex):
const short mode0 = 0;
const short mode1 = 1;
const short mode2 = 2;
const short state0 = 0;
const short state1 = 4;
const short state2 = 8;
so instead of :
set_register(5);
we have:
set_register(state1|mode1);
What I'm looking for is a build time version of:
ASSERT(5==(state1|mode1));
Update
#Christian, thanks for the quick response, I'm interested on a C / non-boost environment answer too because this is driver/kernel code.

NEW ANSWER :
In my original answer (below), I had to have two different macros to support assertions in a function scope and at the global scope. I wondered if it was possible to come up with a single solution that would work in both scopes.
I was able to find a solution that worked for Visual Studio and Comeau compilers using extern character arrays. But I was able to find a more complex solution that works for GCC. But GCC's solution doesn't work for Visual Studio. :( But adding a '#ifdef __ GNUC __', it's easy to choose the right set of macros for a given compiler.
Solution:
#ifdef __GNUC__
#define STATIC_ASSERT_HELPER(expr, msg) \
(!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; }))
#define STATIC_ASSERT(expr, msg) \
extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)]
#else
#define STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
#endif /* #ifdef __GNUC__ */
Here are the error messages reported for STATIC_ASSERT(1==1, test_message); at line 22 of test.c:
GCC:
line 22: error: negative width in bit-field `STATIC_ASSERTION__test_message'
Visual Studio:
test.c(22) : error C2369: 'STATIC_ASSERTION__test_message' : redefinition; different subscripts
test.c(22) : see declaration of 'STATIC_ASSERTION__test_message'
Comeau:
line 22: error: declaration is incompatible with
"char STATIC_ASSERTION__test_message[1]" (declared at line 22)
ORIGINAL ANSWER :
I do something very similar to what Checkers does. But I include a message that'll show up in many compilers:
#define STATIC_ASSERT(expr, msg) \
{ \
char STATIC_ASSERTION__##msg[(expr)?1:-1]; \
(void)STATIC_ASSERTION__##msg[0]; \
}
And for doing something at the global scope (outside a function) use this:
#define GLOBAL_STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]

There is an article by
Ralf Holly that examines different options for static asserts in C.
He presents three different approaches:
switch case values must be unique
arrays must not have negative dimensions
division by zero for constant expressions
His conclusion for the best implementation is this:
#define assert_static(e) \
do { \
enum { assert_static__ = 1/(e) }; \
} while (0)

Checkout boost's static assert

You can roll your own static assert if you don't have access to a third-party library static assert function (like boost):
#define STATIC_ASSERT(x) \
do { \
const static char dummy[(x)?1:-1] = {0};\
} while(0)
The downside is, of course, that error message is not going to be very helpful, but at least, it will give you the line number.

#define static_assert(expr) \
int __static_assert(int static_assert_failed[(expr)?1:-1])
It can be used anywhere, any times.
I think it is the easiest solution.
Before usage, test it with your compiler carefully.

Any of the techniques listed here should work and when C++0x becomes available you will be able to use the built-in static_assert keyword.

If you have Boost then using BOOST_STATIC_ASSERT is the way to go. If you're using C or don't want to get Boost
here's my c_assert.h file that defines (and explains the workings of) a few macros to handle static assertions.
It's a bit more convoluted that it should be because in ANSI C code you need 2 different macros - one that can work in the area where you have declarations and one that can work in the area where normal statements go. There is a also a bit of work that goes into making the macro work at global scope or in block scope and a bunch of gunk to ensure that there are no name collisions.
STATIC_ASSERT() can be used in the variable declaration block or global scope.
STATIC_ASSERT_EX() can be among regular statements.
For C++ code (or C99 code that allow declarations mixed with statements) STATIC_ASSERT() will work anywhere.
/*
Define macros to allow compile-time assertions.
If the expression is false, an error something like
test.c(9) : error XXXXX: negative subscript
will be issued (the exact error and its format is dependent
on the compiler).
The techique used for C is to declare an extern (which can be used in
file or block scope) array with a size of 1 if the expr is TRUE and
a size of -1 if the expr is false (which will result in a compiler error).
A counter or line number is appended to the name to help make it unique.
Note that this is not a foolproof technique, but compilers are
supposed to accept multiple identical extern declarations anyway.
This technique doesn't work in all cases for C++ because extern declarations
are not permitted inside classes. To get a CPP_ASSERT(), there is an
implementation of something similar to Boost's BOOST_STATIC_ASSERT(). Boost's
approach uses template specialization; when expr evaluates to 1, a typedef
for the type
::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed<true>) >
which boils down to
::interslice::StaticAssert_test< 1>
which boils down to
struct StaticAssert_test
is declared. If expr is 0, the compiler will be unable to find a specialization for
::interslice::StaticAssert_failed<false>.
STATIC_ASSERT() or C_ASSERT should work in either C or C++ code (and they do the same thing)
CPP_ASSERT is defined only for C++ code.
Since declarations can only occur at file scope or at the start of a block in
standard C, the C_ASSERT() or STATIC_ASSERT() macros will only work there. For situations
where you want to perform compile-time asserts elsewhere, use C_ASSERT_EX() or
STATIC_ASSERT_X() which wrap an enum declaration inside it's own block.
*/
#ifndef C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
#define C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546
/* first some utility macros to paste a line number or counter to the end of an identifier
* this will let us have some chance of generating names that are unique
* there may be problems if a static assert ends up on the same line number in different headers
* to avoid that problem in C++ use namespaces
*/
#if !defined( PASTE)
#define PASTE2( x, y) x##y
#define PASTE( x, y) PASTE2( x, y)
#endif /* PASTE */
#if !defined( PASTE_LINE)
#define PASTE_LINE( x) PASTE( x, __LINE__)
#endif /* PASTE_LINE */
#if!defined( PASTE_COUNTER)
#if (_MSC_VER >= 1300) /* __COUNTER__ introduced in VS 7 (VS.NET 2002) */
#define PASTE_COUNTER( x) PASTE( x, __COUNTER__) /* __COUNTER__ is a an _MSC_VER >= 1300 non-Ansi extension */
#else
#define PASTE_COUNTER( x) PASTE( x, __LINE__) /* since there's no __COUNTER__ use __LINE__ as a more or less reasonable substitute */
#endif
#endif /* PASTE_COUNTER */
#if __cplusplus
extern "C++" { // required in case we're included inside an extern "C" block
namespace interslice {
template<bool b> struct StaticAssert_failed;
template<> struct StaticAssert_failed<true> { enum {val = 1 }; };
template<int x> struct StaticAssert_test { };
}
}
#define CPP_ASSERT( expr) typedef ::interslice::StaticAssert_test< sizeof( ::interslice::StaticAssert_failed< (bool) (expr) >) > PASTE_COUNTER( IntersliceStaticAssertType_)
#define STATIC_ASSERT( expr) CPP_ASSERT( expr)
#define STATIC_ASSERT_EX( expr) CPP_ASSERT( expr)
#else
#define C_ASSERT_STORAGE_CLASS extern /* change to typedef might be needed for some compilers? */
#define C_ASSERT_GUID 4964f7ac50fa4661a1377e4c17509495 /* used to make sure our extern name doesn't collide with something else */
#define STATIC_ASSERT( expr) C_ASSERT_STORAGE_CLASS char PASTE( PASTE( c_assert_, C_ASSERT_GUID), [(expr) ? 1 : -1])
#define STATIC_ASSERT_EX(expr) do { enum { c_assert__ = 1/((expr) ? 1 : 0) }; } while (0)
#endif /* __cplusplus */
#if !defined( C_ASSERT) /* C_ASSERT() might be defined by winnt.h */
#define C_ASSERT( expr) STATIC_ASSERT( expr)
#endif /* !defined( C_ASSERT) */
#define C_ASSERT_EX( expr) STATIC_ASSERT_EX( expr)
#ifdef TEST_IMPLEMENTATION
C_ASSERT( 1 < 2);
C_ASSERT( 1 < 2);
int main( )
{
C_ASSERT( 1 < 2);
C_ASSERT( 1 < 2);
int x;
x = 1 + 4;
C_ASSERT_EX( 1 < 2);
C_ASSERT_EX( 1 < 2);
return( 0);
}
#endif /* TEST_IMPLEMENTATION */
#endif /* C_ASSERT_H_3803b949_b422_4377_8713_ce606f29d546 */

Try:
#define STATIC_ASSERT(x, error) \
do { \
static const char error[(x)?1:-1];\
} while(0)
Then you can write:
STATIC_ASSERT(a == b, a_not_equal_to_b);
Which may give you a better error message (depending on your compiler).

The common, portable option is
#if 5 != (state1|mode1)
# error "aaugh!"
#endif
but it doesn't work in this case, because they're C constants and not #defines.
You can see the Linux kernel's BUILD_BUG_ON macro for something that handles your case:
#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
When condition is true, this becomes ((void)sizeof(char[-1])), which is illegal and should fail at compile time, and otherwise it becomes ((void)sizeof(char[1])), which is just fine.

Ensure you compile with a sufficiently recent compiler (e.g. gcc -std=c11).
Then your statement is simply:
_Static_assert(state1|mode1 == 5, "Unexpected change of bitflags");

#define MODE0 0
#define MODE1 1
#define MODE2 2
#define STATE0 0
#define STATE1 4
#define STATE2 8
set_register(STATE1|STATE1); //set_register(5);
#if (!(5==(STATE1|STATE1))) //MY_ASSERT(5==(state1|mode1)); note the !
#error "error blah blah"
#endif
This is not as elegant as a one line MY_ASSERT(expr) solution. You could use sed, awk, or m4 macro processor before compiling your C code to generate the DEBUG code expansion of MY_ASSERT(expr) to multiple lines or NODEBUG code which removes them for production.

Related

How do I test if _Static_assert is defined?

_Static_assert is built-in to some C compilers such as gcc and clang, but it may not be included in all C compilers. I would like to use the _Static_assert functionality while keeping my code as cross-platform as possible. I figured the best way to do this was to test
#ifdef _Static_assert
_Static_assert(0, "Test");
#endif
but that doesn't seem to work out. It compiles, but it does not detect that _Static_assert is defined. Then I figured I could test if the compiler was GCC or not, but I read that having __GNUC__ defined doesn't necessarily prove that the compiler used is GCC. This also doesn't detect other compilers where _Static_assert is defined that I may not know about. So my question is, what is the best way to detect if the compiler supports _Static_assert in the preprocessor?
EDIT:
This is the solution I came up with that suits my purposes. Thanks to #KamilCuk below for the link that helped me out.
// Check that we can use built-in _Static_assert
#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
#define WE_HAVE_STATIC_ASSERT 1
#endif
#if WE_HAVE_STATIC_ASSERT
_Static_assert(0, "Test");
#endif
This code works for me on both gcc and clang: https://godbolt.org/z/svaYjWj4j
FINAL EDIT (I think): This provides an answer to my original question about how to detect if _Static_assert is available. It also provides a fallback option that results in relatively helpful errors in most compilers I tested.
Here is the link to the test code: https://godbolt.org/z/TYEj7Tezd
// Check if we can use built-in _Static_assert
#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
#define MY_STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
#else // We make our own
// MY_JOIN macro trick generates a unique token
#define MY_JOIN2(pre, post) MY_JOIN3(pre, post)
#define MY_JOIN3(pre, post) pre ## post
#if defined( __COUNTER__ ) // try to do it the smart way...
#define MY_JOIN(pre) MY_JOIN2(pre, __COUNTER__)
#define MY_STATIC_ASSERT(cond, msg) \
static const char *MY_JOIN(static_assert)[(cond) * 2 - 1] = { msg }
#else // we did our best...
//will break if static assert on same line in different file
#define MY_JOIN(pre) MY_JOIN2(pre, __LINE__)
#define MY_STATIC_ASSERT(cond, msg) \
static const char *MY_JOIN(static_assert)[(cond) * 2 - 1] = { msg }
#endif
#endif
/* - CHANGE CODE HERE TO TEST THE ASSERTIONS - */
enum {
A = 3,
B = 3,
C = B - A
};
/* - --------------------------------------- - */
// Test to see if the enum values match our assertions
MY_STATIC_ASSERT(B > A, "B must be greater than A");
MY_STATIC_ASSERT(C > 0, "C must be greater than zero");
Helpful information I used to make this came from these links:
http://jonjagger.blogspot.com/2017/07/compile-time-assertions-in-c.html
https://www.tutorialspoint.com/cprogramming/c_preprocessors.htm
https://stackoverflow.com/a/43990067/16292858
How do I test if _Static_assert is defined?
_Static_assert is part of C11. So check for C11.
#if __STDC_VERSION__ > 201112L
You could also #include <assert.h> and check for #ifdef static_assert.
My first google hit for static_assert.h github has a nice example how to handle different tools and compilers: https://github.com/wc-duck/dbgtools/blob/master/include/dbgtools/static_assert.h#L68 .
If you want to write a C11 compatibility layer and use static assertion in your code, for example use this answer and fallback to your own wrapper:
// static_assert.h
#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond) \
typedef struct { int static_assertion_failed : !!(cond); } \
CTASTR(static_assertion_failed_,__COUNTER__)
#include <assert.h>
#ifndef static_assert
#define static_assert(expr, str) STATIC_ASSERT(expr)
#endif
// somefile.c
#include <static_assert.h>
static_assert(something == something, "Uwu");

single-line #ifdef in C/C++

Consider the following simple C/C++ example:
#define FOO
...
#ifdef FOO
bar++;
#endif
OK, now I would like to fit that (and any other similar) conditional into one line for the code readability sake (the code has tens of single-line statements that all need to be conditional each depending on different define). Something that, when used, would look like:
#define FOO
...
MY_IFDEF(FOO,bar++;) //Single-line conditional
The goal is to have a reusable macro that can take an arbitrary identifier and, compile the statement if such identifier has been #define-d previously, and do it all in a single line.
Any ideas?
UPDATE0: the code must compile for both C and C++
You can't use #ifdef while expanding macros, but you can totally precheck it and declare empty statement if condition is not met.
#ifdef FOO
#define MY_IFDEF(x,y) some-processing-you-need
#else
#define MY_IFDEF(x,y) ;
#endif
Also check out new feature of C++: constexpr this can also be usable.
UPDATE4: As pointed out by #Alex Bakanov you can't use #ifdef while expanding macros, so there is no single line solution for your question which works in all cases. Nevertheless, I hope the idea I wrote here may be useful.
if you use #define FOO 1 or #define FOO 0, combination of #define and if constexpr can be used. Note that it gives an error if FOO is not defined. This program gives 1 as result:
#include<iostream>
#define FOO 1
#define MY_IFDEF(x,y) if constexpr (x) y;
int main()
{
int bar =0;
MY_IFDEF(FOO,bar++)
std::cout << bar << "\n";
}
UPDATE: Based on #eerorika's comment to avoid the error if FOO is not defined, the following declaration has to be added:
constexpr bool FOO = false;
UPDATE2: This version works in any circumstances, the only question is that is it worth the effort?
#ifdef FOO
constexpr bool USED_FOO = true;
#else
constexpr bool USED_FOO = false;
#endif
#define MY_IFDEF(x,y) if constexpr (USED_##x) y ;
UPDATE3: C compatible version. Note that in this case in theory it is evaluated runtime not compile time, but the compiler will realize that it is always true/false and generates the code accordingly:
#ifdef FOO
const static bool USED_FOO = true;
#else
const static bool USED_FOO = false;
#endif
#define MY_IFDEF(x,y) if (USED_##x) y;

Use specific entry of an X macro

I am using X macros to generate functions setting GPIOs to 0 or 1 (I generate around 60 functions to set around 30 GPIOs). Here is an example (I have just written this example, so the syntax may be wrong):
/* X(pin_name, pin_nb) */
#define CPLD_GPIOs \
X(Pin0, 0) \
X(Pin1, 1) \
X(Pin2, 2) \
X(Pin3, 3)
I generate the functions to access to these GPIOs:
#define X(pin_name, pin_nb) \
static void SetOn_GPIO##pin_name (void) { \
SetOn_GPIOpins(pin_nb);\
}
CPLD_GPIOs
#undef X
The same process exists for SetOff_GPIOXXX functions.
Is there a way I can access the function generated above by the compiler as SetOn_GPIOPin2 in an other part of the program without directly writing the function name? (In order to keep the code as global as possible)
At the end of preprocessing, we should only have SetOn_GPIOPin2(); (and not every X-macro entries) generated from X-Macro.
Before pre-processing:
void foo ()
{
/* some code */
/*
* Macro to generate the desired function.
* For e.g: SetOn_GPIOPin2();
*/
/* some code */
}
After pre-processing:
void foo ()
{
/* some code */
/* Function resulting of the pre-processing */
SetOn_GPIOPin2();
/* some code */
}
From comments on the question, your objective appears to be to protect against the case in which your X macro is modified to produce differently-named functions. If that's so, then I think you're making unnecessary work for yourself: whether those names are changed is under your (and any other project developers') control, and a name change such as you are concerned about will not go unnoticed very long. So don't change them.
But if you're determined to go this route then no, there is no way to make the preprocessor extract the function names generated by your macros from their larger replacement text. Instead, you would need to inject them, via the same macro, into both the X macros and your other code. Like this, perhaps:
/* X(pin_name, pin_nb) */
#define CPLD_GPIOs(gen) \
X(Pin0, 0, gen) \
X(Pin1, 1, gen) \
X(Pin2, 2, gen) \
X(Pin3, 3, gen)
// Generates the wanted function names:
#define GPIO_ON(pin_name, pin_nb) SetOn_GPIO##pin_name
#define X(pin_name, pin_nb, gen) \
static void gen(pin_name, pin_nb) (void) { \
SetOn_GPIOpins(pin_nb);\
}
CPLD_GPIOs
#undef X
// ...
void some_function(void) {
GPIO_ON(pin_name, pin_nb)();
}
But note well that although this technique might have other applications, such as to generating multiple sets of functions with the same set of X macros, it just kicks the can down the road with respect to the specific objective you described. You can rely on the name-generator macro to produce the same names for function declarations and function calls, but you still have the problem that the X macro can be modified to generate function declarations with different names.
Using our trusty Boost.Preprocessor rocket launcher, this is preatty easy:
#include <boost/preprocessor/tuple/rem.hpp>
#include <boost/preprocessor/control/if.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#define X_SELECT_PIN(pin_nb, selected_pin_nb, ...) \
BOOST_PP_TUPLE_REM_CTOR(BOOST_PP_IF( \
BOOST_PP_EQUAL(pin_nb, selected_pin_nb), \
(__VA_ARGS__), \
() \
))
// Usage
#define X(pin_name, pin_nb)\
X_SELECT_PIN(pin_nb, 2, SetOn_GPIO##pin_name (void);)
CPLD_GPIOs
#undef X
This uses BOOST_PP_IF to expand your pattern only for the selected pin. The added parentheses and BOOST_PP_TUPLE_REM_CTOR are there to protect the macro from expansions containing commas.

Compile time assertion as part of an expression but without _Static_assert

Ultimately, I want a compile-time-const macro that in itself includes an assertion.
With a real _Static_assert, I can do something like
#define CEXPR_MACRO_WITH_ASSERTION(Assertion) sizeof(struct{char c; _Static_assert(Assertion,""); })?0:42
(meant for stuff like "compile-time-assert" that the macro value computation won't overflow on any target, and I'd like to keep the assertion in the macro so that it's tightly coupled with the value) but compilers like tcc don't have static asserts so I'd need to emulate it.
#define STATIC_ASSERT(Cexpr,Msg) extern STATIC_ASSERT[(Cexpr)?1:-1]
is a common way to do it but with that extern I can't use it in a struct so I could split it in two
#define STATIC_ASSERT(Cexpr,Msg) extern STATIC_ASSERT_(Cexpr,Msg)
#define STATIC_ASSERT_(Cexpr,Msg) char STATIC_ASSERT[sizeof(char [((Cexpr))?1:-1])] /*ignore Msg for simplicity's sake*/
and use the underscore version in the CEXPR_MACRO_WITH_ASSERTION, but in function context, this will give false positives on compilers that support structs with VLAs in them:
#define STATIC_ASSERT(Cexpr,Msg) extern STATIC_ASSERT_(Cexpr,Msg)
#define STATIC_ASSERT_(Cexpr,Msg) char STATIC_ASSERT[sizeof(char [((Cexpr))?1:-1])]
#define CEXPR_MACRO_WITH_ASSERTION(Assert) (sizeof(struct{char c; STATIC_ASSERT_(Assert,""); })?0:42)
int main(void)
{
int x = 0;
CEXPR_MACRO_WITH_ASSERTION(x);
} //compiles on tcc and gcc (clang rejects it because of the vla in a struct)
so I effectively need:
#define STATIC_ASSERT_(Cexpr,Msg) char STATIC_ASSERT[sizeof(char [((Cexpr)&&ENFORCE_ICEXPR(Cexpr))?1:-1])]
Now I realize on tcc in particular, ENFORCE_ICEXPR (enforce integer constant expression) could be simply replaced with __builtin_constant_p but I was curious if I could do it without the platform dependency.
So I thought I could test Cexpr by trying to assign it to an enum constant and I came up with:
#define ENFORCE_Z(X) _Generic(0LL+(X),ullong:(X),llong:(X)) /*could be just `+(X)` cuz I don't care about floats*/
#define ENFORCE_ICEXPR(X) sizeof( void (*)(enum { ENFORCE_ICEXPR = (int)ENFORCE_Z(X) } ) )
but this gets gcc and clang complaining (unsilencably in gcc's case) about the enum not being visible outside of the declaration (which, incidentally, was the intention here) so I resorted to
#define ENFORCE_ICEXPR(X) sizeof(enum { BX_cat(ENFORCE_ICEXPR__,__COUNTER__) = (int)ENFORCE_Z(X) })
relying on the nonstandard magic macro, __COUNTER__.
My question is, is there a better way to write ENFORCE_ICEXPR(X)?
Perl uses a bit-field instead of an array to define a static_assert fallback:
#define STATIC_ASSERT_2(COND, SUFFIX) \
typedef struct { \
unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \
} _static_assertion_failed_##SUFFIX PERL_UNUSED_DECL
#define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX)
#define STATIC_ASSERT_DECL(COND) STATIC_ASSERT_1(COND, __LINE__)
No compiler implements variable length bit-fields.

Variadic macros didn't work

What I want to do is access code with macros. But the complier gives me this error
identifier "BUTTON___button" is undefined
#define BUTTON_1 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8)
#define BUTTON_2 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_9)
#define BUTTON_3 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_4)
#define BUTTON_4 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5)
#define BUTTON_5 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)
#define BUTTON_6 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_14)
#define BUTTON_7 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_12)
#define BUTTON_8 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_11)
#define BUTTON_9 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_15)
#define BUTTON_10 HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_0)
#define BUTTON_11 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_10)
#define BUTTON_12 HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_15)
#define BUTTON_13 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1)
#define BUTTON_14 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2)
#define BUTTON_15 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_11)
#define BUTTON_16 HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2)
#define BUTTON_17 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_0)
#define BUTTON_18 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_1)
#define BUTTON_19 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5)
#define BUTTON_20 HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4)
#define BUTTON(...) BUTTON_##__VA_ARGS__
for(uint8_t _button = 1; _button < 21; _button++)
BUTTON(__button) //=> This should give me BUTTON(1) , BUTTON(2) ... each iteration.But not working
By using variadic macros, can I get what I want?
You must remember that preprocessor things happen before the code is compiled, and is strictly text-only replacement.
Thus, depending on a variable that has different values because of a run-time loop makes no sense, and doesn't work.
The proper way to do this is to put the port addresses (GPIOA etc) in an array, together with the corresponding pin for each port:
static const struct {
const GPIO_TypeDef *port;
uint32_t pin;
} buttons[] = {
{ GPIOB, GPIO_PIN_8 },
{ GPIOB, GPIO_PIN_9 },
...
};
then iterate over the data in the array:
for (size_t i = 0; i < sizeof buttons / sizeof *buttons; ++i)
{
if (HAL_GPIO_ReadPin(buttons[i].port, buttons[i].pin))
{
...
}
}
The preprocessor works at compile-time, and is involved very early (read about translation phases). So you can't generate macros with a for loop. Read documentation of cpp and later your C standard (e.g. n1570).
You could generate your C code with a different program -maybe some other preprocessor like GPP or m4, or some script (or your own other program), and generating C files is a common practice since the previous century (for examples, look into yacc or rpcgen, but you'll find many others). You then want to configure your build automation tool (perhaps make or ninja) to invoke such a C code generator appropriately.
You can obtain the preprocessed form, e.g. with gcc -C -E if using GCC. So you can understand what the compiler (its translation phases after preprocessing) is getting.
By using variadic macros, can I get what I want?
No you can't. Read documentation of variadic macros.
Variadic macros won't solve anything here. You have two options, look-up tables or X macros. Look-up tables are preferable since they are most readable. But in case you need to minimize code repetition, X macros can be useful, although they are a bit hard to read.
Look-up table version:
typedef struct // I'm not sure about the exact types used here
{
volatile uint8_t* port;
uint8_t pin;
} button_t;
const button_t BUTTON[20] =
{
{&GPIOB, 8},
{&GPIOB, 9},
...
};
for(uint8_t i = 0; i<20; i++)
{
HAL_GPIO_ReadPin(BUTTON[i].port, BUTTON[i].pin);
}
X macro version:
#define BUTTON_LIST \
/*button port pin */ \
X(1, B, 8) \
X(2, B, 9) \
X(3, B, 4) \
X(4, B, 5) \
X(5, C, 13)
...
for(uint8_t button = 0; button<20; button++)
{
#define X(button, port, pin) HAL_GPIO_ReadPin(GPIO##port, GPIO_PIN_##pin);
BUTTON_LIST
#undef X
}

Resources