【问题标题】:How does function-like macros work, this example类似函数的宏是如何工作的,这个例子
【发布时间】:2021-02-26 09:34:05
【问题描述】:
void someOtherFunction(void)
{
    ...
}


//Is it possible to call the #define like this, in the global scope? When does the execution come here?
SYSTEM_CONTROL_REGISTER_INIT_FUNCTION( credHandlerInit, LEVEL_APPLICATION, SYSTEM_CONTROL_ORDER_DONT_CARE ); 

void credHandlerInit(void)
{
    portBASE_TYPE result;

    result = xTaskCreate( credHandlerTask,
                        (portCHAR *) MBS_CFG_TASK_NAME_CRED_HANDLER,
                        MBS_CFG_TASK_STACK_CRED_HANDLER,
                        NULL,
                        MBS_CFG_TASK_PRIO_CRED_HANDLER,
                        &credHandlerTaskHandle );

}

在 .h 文件中定义了以下宏:

#define SYSTEM_CONTROL_REGISTER_INIT_FUNCTION( _initFunctionName,                                                       \
                                               _initLevel,                                                              \
                                               _initOrder )                                                             \
                                                                                                                        \
    void _initFunctionName( void );                                                                                     \
                                                                                                                        \
    SystemControlInitListRecord const systemControlInitRecord_ ## _initLevel ## _initOrder ## _ ## _initFunctionName    \
                    __attribute__ ((section (".systemControlInitList")))                                                \
                    = {                                                                                                 \
                        .name                   = #_initFunctionName,                                                   \
                        .syncedInitFunction     = NULL,                                                                 \
                        .unsyncedInitFunction   = _initFunctionName,                                                    \
                        .level                  = _initLevel,                                                           \
                        .order                  = _initOrder,                                                           \
                        .initType               = SYSTEM_CONTROL_RTOS_RUNNING                                           \
                      };                                                                                                \
                                                                                                                        \
    void _initFunctionName( void )

我不明白的是这个函数是怎么调用的? 我在代码中没有看到对此函数的任何调用。 有人能解释一下这是如何工作的吗?

下面的代码是否有效,这样调用宏?

//main.c

#define SOMETHING(x) (someVariable = x)

static uint32_t someVariable;

SOMETHING(5);

int main(void)
{
  printf("%d", someVariable); //should print 5 here then?
}

【问题讨论】:

  • 阅读 C 编译器的文档。尝试GCC,阅读其文档并阅读GNU cpp 的文档,其中解释了您的代码。如果允许在您的translation-unit.c 上使用 GCC,调用它(例如 gcc -Wall -H -I/usr/local/include/ -O -C -E translation-unit.c > translation-unit.i)以获取预处理的表单,并使用编辑器查看 translation-unit.i 内部(例如 GNU emacs...)
  • 对于下一个问题,提供一些minimal reproducible example 并给出编译命令。之前,请参考this C reference 网站。您可能还想阅读一些 C 标准,例如 n1570 或更好
  • 带有宏的开源 C 代码示例包括 the Linux kernelGNU emacsBismonnwcc compiler。你可以下载他们的源代码并学习它以获得灵感
  • 你也可以阅读Modern C这本书。它有专门介绍预处理器的章节(特别是第 16 节)
  • 这很像一个关于如何编写类似函数的宏的教程。由于类似函数的宏调用与宏使用的参数数量不匹配,因此代码也没有意义。这是真实代码的复制/粘贴吗?

标签: c macros embedded


【解决方案1】:

这段代码的意图如下:

  • 宏声明了一个名为credHandlerInit 的函数。此名称被传递给_initFunctionName 宏参数名称。编写宏的人不太清楚他们在做什么,所以我们最终得到了两个函数声明void credHandlerInit(void );,这还可以,但很可疑。也许他们可以选择地表示宏后面跟{ ... } 形式的函数定义。无论如何,这不是最好的主意。

  • 整个SystemControlInitListRecord const 部分声明了该类型的只读结构,然后将其命名为systemControlInitRecord_LEVEL_APPLICATION_SYSTEM_CONTROL_ORDER_DONT_CARE _credHandlerInit。这就是我开始怀疑写这篇文章的程序员按每写一封信付费的地方......而且他们不是 C 编程专家。

    注意:超过 32 个字符的标识符在 C 中存在安全隐患,请参阅 5.2.4.1 翻译限制。因此,如果这个函数名将作为一个外部标识符,我们有充分的理由相信它不是static,那么符合标准的编译器可能无法区分带有systemControlInitRecord_LEVEL_APPLICATION_ 前缀的不同函数。无法保证“名称修改”,但编译器最终可能会生成从外部调用方调用错误函数的源代码。

    在实践中,现代编译器倾向于区分远多于 32 个字符,但不能保证这样做。最好的情况是,代码在标准编译器之间是不可移植的。最坏的情况是,整个代码在指定的编译器上会完全失控。

    所以这是一个原始程序员没有意识到的微妙而严重的错误。有人将需要从发明如此可笑的长标识符名称中抽出他们。它既危险又完全不可读。

  • __attribute__ ((section (".systemControlInitList"))) 是 gcc 和许多其他编译器用于创建自定义内存段的常见非标准扩展。这些名称需要与链接描述文件中的内存部分相对应。为什么这个变量需要驻留在那个内存部分,我不知道,但这显然是一些嵌入式系统的一部分,其中命名的内存部分是相当普遍的做法(用于引导加载程序、片上库代码、NVM 变量、ISR、闪存驱动程序等)。

  • 整个{ .name = #_initFunctionName, .syncedInitFunction = NULL, ... 部分是使用_designated 初始化程序的结构初始化程序列表。第一个成员显然是一个字符串,#“stringification”运算符将名称字符串转换为"credHandlerInit"

  • void _initFunctionName( void ),如前所述,可以选择开始函数定义,然后显然希望在调用方继续。否则宏调用中的; 会使其成为第二个函数声明。

正如您希望从我的 cmets 中看到的那样,这段代码是由一个痴迷于使事情尽可能不必要地复杂的人编写的,而真正优秀的 C 程序员却试图让事情尽可能简单。

我猜它是一些难闻的 RTOS 源代码的一部分,用于创建用户端任务或类似的东西?我会远离这个来源,因为有很多代码异味,而且我已经发现一个严重的错误,只是阅读一个宏。

【讨论】:

  • 感谢您的详细解释。但它并没有真正解释它是如何在头文件的#define 中结束的。两个函数之间的界线会发生什么?那是对宏的“调用”吗?
  • @Bei 是的,它是宏调用,它执行答案中项目符号列表中的所有事情。
  • 谢谢。看看我下面的答案和问题(无法在这里以好的方式发布代码)
  • @Bei 好吧,这个宏当然是有效的,虽然是不好的做法。我建议在处理更高级的代码(例如这种混乱)之前阅读 C 书籍中的宏基础知识。
  • 问题是,我还没有写这个废话。我正在研究一个令人兴奋的代码库,只是想了解这种老式的编码风格。我什至在代码中看到了“goto”。感觉有点古老
【解决方案2】:

“类函数宏”仅表示接受参数的宏。

在这个例子中,你有一个宏,它接受参数,但它根本没有像函数一样使用。

此宏扩展为常量对象定义。对象是一个结构,其中包含有关函数以及何时调用它的信息。该对象放置在名为“.systemControlInitList”的自定义非标准部分中。

为了使用此代码,您必须有一个相应的自定义链接器控制脚本(通常称为 something.ld)。这将收集放置在自定义部分中的所有对象,并将它们放在程序映像中的某个位置,可能是 ROM。它会在此类对象块的开头放置一些自定义符号,并可能在末尾放置另一个符号。

然后需要一些自定义代码作为应用程序启动或引导序列的一部分运行,以查找该符号并执行结构数组引用的所有函数。

【讨论】:

    猜你喜欢
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 2019-12-12
    • 2021-07-24
    • 1970-01-01
    • 2021-10-05
    • 2017-11-26
    • 2021-10-14
    相关资源
    最近更新 更多