4

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?

asked Jun 22, 2022 at 22:12
1
  • 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. Commented Aug 28, 2022 at 2:57

1 Answer 1

4

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.

answered Jun 23, 2022 at 16:08

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.