【问题标题】:Using the C preprocessor to determine current scope?使用 C 预处理器确定当前范围?
【发布时间】:2012-08-15 22:33:50
【问题描述】:

我正在用 C / Objective-C 开发一个应用程序(请不要使用 C++,我已经有一个解决方案),我遇到了一个有趣的用例。

因为clang不支持嵌套函数,所以我原来的做法不行:

#define CREATE_STATIC_VAR(Type, Name, Dflt) static Type Name; __attribute__((constructor)) void static_ ## Type ## _ ## Name ## _init_var(void) { /* loading code here */ }

这段代码使用 GCC 可以很好地编译,但是由于 clang 不支持嵌套函数,我得到一个编译错误:

预期的';'在声明结束时。

所以,我找到了一个适用于函数内部变量的 Clang 解决方案:

#define CREATE_STATIC_VAR_LOCAL(Type, Name, Dflt) static Type Name; ^{ /* loading code here */ }(); // anonymous block usage

但是,我想知道是否有一种方法可以利用宏连接来选择适合这种情况的连接,例如:

#define CREATE_STATIC_VAR_GLOBAL(Type, Name, Dflt) static Type Name; __attribute__((constructor)) void static_ ## Type ## _ ## Name ## _init_var(void) { /* loading code here */ }
#define CREATE_STATIC_VAR_LOCAL(Type, Name, Dflt) static Type Name; ^{ /* loading code here */ }(); // anonymous block usage

#define SCOPE_CHOOSER LOCAL || GLOBAL
#define CREATE_STATIC_VAR(Type, Name, DFLT) CREATE_STATIC_VAR_ ## SCOPE_CHOOSER(Type, Name, Dflt)

显然,结束的实现不必完全一样,但类似的就足够了。

我曾尝试将__builtin_constant_p__func__ 一起使用,但因为__func__ 不是编译时常量,所以这不起作用。

我也尝试过使用__builtin_choose_expr,但这似乎不适用于全局范围。

我在文档中还缺少什么吗?看起来这应该是一件相当容易做到的事情,但我似乎无法弄清楚。

注意:我知道我可以简单地输入 CREATE_STATIC_VAR_GLOBALCREATE_STATIC_VAR_LOCAL 而不是搞乱宏连接,但这是我试图推动编译器的极限。我也知道我可以使用 C++ 并立即解决这个问题,但这不是我的目标。

【问题讨论】:

  • clang 根本不支持嵌套函数吗?对于 GCC,有 -fnested-functions 开关,不是可用于 clang 吗?顺便说一句,为什么在编译时需要这个?检查 func 是否为 NULL 或空字符串可以完成这项工作,不是吗?
  • @H2CO3 是的,这是一个宏,用于创建从配置加载的静态变量,它需要在编译时。至于 clang 不支持嵌套函数,请阅读:clang.llvm.org/docs/UsersManual.html#c_unimpl_gcc
  • 谢谢,这很有趣(也很抱歉......)
  • 顺便说一句,你想用这个来实现什么?你不应该重新设计你的代码而不是 CPP 黑客吗?

标签: scope global-variables clang static-variables nested-function


【解决方案1】:
#define SCOPE_CHOOSER LOCAL || GLOBAL
#define CREATE_STATIC_VAR(Type, Name, DFLT) CREATE_STATIC_VAR_ ## SCOPE_CHOOSER(Type, Name, Dflt)

这里最大的困难是 C 预处理器通过文本替换工作,所以即使你知道如何让 SCOPE_CHOOSER 做你想做的事,你最终会得到一个看起来像这样的宏扩展

CREATE_STATIC_VAR_LOCAL || GLOBAL(Type, Name, Dflt);

在替换期间没有办法让预处理器“恒定折叠”宏扩展;只有当它们出现在#if 表达式中时才会“折叠”。所以你唯一的希望(模数轻微的手摇)是找到一个可以在函数内部和外部工作的单一结构。

您能在这里详细解释一下最终目标吗?我不认为您可以使用__attribute__((constructor)) 加载变量的初始值,但也许有一种方法可以在第一次输入函数体时加载初始值......或者将这些变量的所有地址注册到全局列表中在编译时并有一个遍历该列表的 __attribute__((constructor)) 函数......或这些方法的一些混搭。我没有任何具体的想法,但如果你提供更多信息,也许会出现一些东西。

编辑: 我认为这对你也没有帮助,因为它不是预处理器技巧,但这里是一个常量表达式,在函数范围内计算为 0,在全局范围。

#define AT_GLOBAL_SCOPE __builtin_types_compatible_p(const char (*)[1], __typeof__(&__func__))

但是,请注意我说的是“评估”而不是“扩展”。这些结构是编译时的,而不是预处理时的。

【讨论】:

  • 是的,您可以在constructor 块中设置一个初始值,我在How Tto write an iOS app purley in C? 的回答中使用了它。
  • SCOPE_CHOOSER 应该扩展到 LOCALGLOBAL 之一,具体取决于当前范围。
  • @RichardJ.RossIII #1 — 我的意思是“我认为你不能用 __attribute__((constructor)) 解决你的问题。”显然你可以做你已经做过的事情,但这并没有解决你的问题。还有#2——你能给出任何预处理宏的例子吗?根据 any 排序的上下文,这些宏扩展为两种不同事物中的“一种”?我的观点是宏 expansion 不如表达式 evaluation 强大;将求值 的宏标记粘贴到正确的事物上并没有帮助,除非您实际上可以将其扩展 到正确的事物(仅此而已)。
  • 这是一个完全使用预处理器的 IDEone 示例:ideone.com/5vHOt。如您所见,它根据传入的参数数量创建一个字符串。使用相同的逻辑,如果我们能以某种方式找到一种方法来根据我们是否在函数范围内传递一定数量的参数,我们可以使用相同的东西。
  • “我不认为你可以用 __attribute__((constructor)) 加载变量的初始值”——嗯,我刚刚在我的一个企业应用程序中使用了这种技术,所以它必须工作。 ..
【解决方案2】:

灵感来自@Qxuuplusone answer

AT_GLOBAL_SCOPE 的建议宏确实有效(在 GCC 中),但会导致编译器警告(我很确定它不能被 Diagnostic Pragma 静音,因为它是由 pedwarn 通过测试 here 创建的)。

除非你打开-w,否则你会一直看到这些警告,并且在你的脑海里有一种可怕的感觉,你可能不应该做你正在做的任何事情。

幸运的是,有一个解决方案可以消除这些挥之不去的疑虑。 在Other Builtins 部分,__builtin_FUNCTION 有这个非常有趣的描述(强调我的):

此函数等效于__FUNCTION__ 符号,并返回一个地址常量,该地址常量指向调用内置函数的函数的名称,如果调用不是,则返回空字符串在函数范围内

事实证明,至少在 GCC 8.3 版本中,您可以这样做:

#define AT_GLOBAL_SCOPE (__builtin_FUNCTION()[0] == '\0')

这可能仍然无法回答最初的问题,但在 GCC 决定这也会导致警告之前(这似乎是故意设计的不是),它让我继续做使用宏有问题的事情没有任何警告我这是一个坏主意。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-08
    • 1970-01-01
    • 1970-01-01
    • 2018-02-10
    • 1970-01-01
    • 2021-12-29
    • 2017-01-05
    相关资源
    最近更新 更多