【问题标题】:Can I create a macro which conditionally `goto`s and serves as an expression?我可以创建一个有条件地“goto”并用作表达式的宏吗?
【发布时间】:2019-09-08 17:46:25
【问题描述】:

是否可以创建一个扩展以下代码的宏

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

变成语义上等价(足够)的东西

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

我担心这是不可能的原因是goto label; 是一个语句,我真的需要一个表达式。例如。像

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

因为这个原因不起作用(并且因为 tmp 没有声明,可能还有更多)。

不幸的是,编译器扩展只有在 GCC、VSC 德州仪器的 C 编译器支持时才有可能。

背景

我们有一个现有的代码库,其中每个函数返回一个整数错误代码,调用者在非零返回代码上跳转到cleanup:。我考虑引入使用全局错误代码来启用其返回值的函数,但希望以安全和向后兼容的方式这样做。 The full picture on SE Code Review

【问题讨论】:

  • 编译器扩展是否满足您的需要?
  • @StoryTeller:很可能不会:它需要与 GCC、VSC 和德州仪器 C 编译器一起运行。
  • 您通常可以使用宏创建try/catch/finally 功能,这些宏在底层使用丑陋的setjmp/longjmp 魔法(例如something like this probably,未经测试)。 This thread 做了类似的事情。在 MSVC 中,您将使用 __try__finally,这显然是不可移植的。
  • 另外,调用一个名为division 的函数会改变一个global_error_variable 是你更大的问题。我将首先重新设计您的程序以避免访问全局状态,并避免创建会使代码维护者生气的宏。
  • @Groo:我选择division 是为了有一个可能失败的简单示例(除以零),但实际上这些函数并不纯粹,并且包含“文件打开错误”中的所有内容以“内存分配失败”作为可能的错误条件。我正在调查setjmp/longjmp

标签: c macros c-preprocessor


【解决方案1】:

我可以创建一个有条件地gotos 并用作表达式的宏吗?

不作为表达。考虑一种不同的方法,它只需要稍微重构,但将与标准 C 兼容:

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

那么你会:

#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)

当 GNU 扩展没问题时,您还可以添加带有语句表达式的 GNU 版本:

#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

可以按照您的意愿使用:

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

库的用户可以选择他想要的版本,这取决于用户打算支持的编译器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-04-25
    • 2011-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-02
    相关资源
    最近更新 更多