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?

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.