3

Is it possible to create a macro that expands the following code

int error = 0;
struct parse_tree res = CLEANUP_parse(file_hdl);
// ...
cleanup:
return error

into something semantically equivalent (enough) to

int error = 0;
struct parse_tree res;
struct parse_tree tmp = parse(file_hdl);
if(global_error_variable != 0) {
    error = global_error_variable;
    goto cleanup;
} else {
    res = tmp;
}
// ...
cleanup:
return error

The reason I fear it's impossible is that goto label; is a statement, and I really need an expression. E.g. something like

#define CLEANUP_parse(file_hdl) ((tmp=parse(file_hdl)) ^ tmp ^ global_error_variable) ? goto cleanup : tmp

doesn't work for that reason (and because tmp is not declared and possibly more).

Unfortunately compiler extensions are only possible if GCC, VSC and Texas Instrument's C compiler support them.

Background

We have an existing codebase where each function returns an integer error code and the caller jumps to cleanup: on non-zero return code. I consider introducing functions that use a global error code to enable the use of their return value, but want to do to in a safe and backwards compatible manner. The full picture on SE Code Review

Community
  • 1
  • 1
Perseids
  • 12,584
  • 5
  • 40
  • 64
  • Are compiler extension acceptable for you needs? – StoryTeller - Unslander Monica Apr 18 '19 at 11:37
  • @StoryTeller: Very likely not: It needs to run with GCC, VSC, and the Texas Instrument C compiler. – Perseids Apr 18 '19 at 11:39
  • You can often create `try/catch/finally` functionality using macros which use the ugly `setjmp`/`longjmp` magic under the hood (e.g. [something like this probably](https://gist.github.com/rampion/91594), not tested). [This thread](https://stackoverflow.com/q/10586003/69809) does a similar thing. In MSVC you would use `__try` and `__finally`, which are obviously non portable. – vgru Apr 18 '19 at 11:42
  • Also, the fact that calling a function called `division` mutates a `global_error_variable` is your bigger problem. I would start by redesigning your program to avoid accessing global state, and avoid creating macros which would make the maintainer of your code angry. – vgru Apr 18 '19 at 11:44
  • @Groo: I choose `division` with the intent of having a simple example which can fail (division by zero), but in truth those functions are not pure and have everything from "file open error" to "memory allocation failed" as possible error conditions. I am looking into `setjmp`/`longjmp` though. – Perseids Apr 18 '19 at 11:50

1 Answers1

1

Can I create a macro which conditionally gotos and serves as an expression?

Not as an expression. Consider a different approach, that requires only a slight refactoring, but will be compatible with standard C:

int error = 0;
struct parse_tree res;
CLEANUP_PARSE(res, file_hdl, error, cleanup);
// ...
cleanup:
return error;

then you would:

#define CLEANUP_PARSE(res, file_hdl, error, cleanup) \
do { \
    res = parse(file_hdl); \
    if (global_error_variable != 0) { \
       error = global_error_variable; \
       goto cleanup; \
     } \
} while(0)

When GNU extensions are ok, you would also add a GNU version with statement expressions:

#ifdef __GNUC__
// https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs
#define CLEANUP_PARSE_RET(file_hdl, ...) \
__extension__({ \
    struct parse_tree _res; \
    /* macro from above */ \
    CLEANUP_PARSE(_res, file_hdl, __VA_ARGS__); \
    _res; \
})
#endif

that could be used as you intended:

struct parse_tree res = CLEANUP_PARSE_RET(file_hdl, error, cleanup);

And the user of the library can choose the version he wants, depending on which compiler the user intents to support.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111