0

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 Answer 1

0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.