I have multiple use cases where I need to call an "entry" function, do a bunch of stuff, and then call an "exit" function. (In my case, "entry" is enabling a chip select line and "exit" is disabling it.)
I'm tempted to do the following, but it seems too obvious. How might this trip me up?
#define WITH_CHIP_SELECTED(...) \
ChipSelect(true); \
__VA_ARGS__ \
ChipSelect(false);
// usage:
WITH_CHIP_SELECTED(
BeginTheBeguine();
GetOutOfTown();
BrushUpYourShakespeare();
DontFencMeIn();
)
According to godbolt, the C-preprocessor will expand to:
ChipSelect(true);
BeginTheBeguine();
GetOutOfTown();
BrushUpYourShakespeare();
DontFencMeIn();
ChipSelect(false);
Yes, I know longjump and non-local exits could circumvent calling the exit function, but that is not an issue here. Otherwise, this seems ideal for what I want. Am I missing something fundamental?
-
A more general routine would query the chip select status first and lastly, not set chip select to false, but restore it to what it was.chux– chux2022年08月28日 02:57:43 +00:00Commented Aug 28, 2022 at 2:57
1 Answer 1
Does this work? Yes, probably. By using variadic macros, the usual errors about commas in the arguments will not occur. However, this approach might not be very good for debuggability – this will depend a lot on your particular tools though.
The best and most maintainable solution to do resource management is to write that part of the code in C++, so that you can use the RAII idiom, or at least use templates and lambdas. C++ has features that avoid common pitfalls with macros.
But let's say we have to stick with C. The most C-ish solution would likely be to not write that macro at all. If cleanup is needed after a section, it would be called explicitly. C culture is somewhat opposed to such abstractions.
But we can use the macro system to write a better abstraction that doesn't rely on passing the contents as macro arguments. For example, we might enable usage such as:
WITH_CHIP_SELECTED {
BeginTheBeguine();
GetOutOfTown();
BrushUpYourShakespeare();
DontFencMeIn();
}
This can be achieved by writing a for-loop that executes once. Roughly:
ChipSelect(true);
for (bool _with_chip_selected_once = true
; _with_chip_selected_once
; _with_chip_selected_once = false, ChipSelect(false)
) {
...
}
A simpler but less ergonomic alternative would be to expect the inner section to be defined as a separate function, then:
WITH_CHIP_SELECTED(do_stuff(context));
Which I'd implement with the idiomatic do-while-false wrapper:
#define WITH_CHIP_SELECTED(call) \
do { \
ChipSelect(true); call; ChipSelect(false); \
} while (0)
Yes, this would happen to allow multiple statements, but it is intended for a different usage style.