【问题标题】:C Macros function definition syntax questionC 宏函数定义语法问题
【发布时间】:2020-08-17 20:15:08
【问题描述】:

我一直在查看一个名为 hickit 的程序,并且在某一时刻(count.c,函数从第 105 行开始),它们从 Klib 库中调用一个宏函数 (kavl_insert),如下所示:

static void hk_count_nei2_core(int32_t n_pairs, struct cnt_nei2_aux *a, int r1, int r2)
{
    struct cnt_nei2_aux *root = 0;
    int32_t i, j, left;
    unsigned cl;
    left = 0;
    kavl_insert(nei2, &root, &a[0], 0);
...

查看Klib库(更具体地说,在kavl.h),这个函数(我认为)定义如下:

#define __KAVL_INSERT(suf, __scope, __type, __head, __cmp) \
    __scope __type *kavl_insert_##suf(__type **root_, __type *x, unsigned *cnt_) { \

稍后在 kavl.h 文件中有这个独立的行(第 322 行):

#define kavl_insert(suf, proot, x, cnt) kavl_insert_##suf(proot, x, cnt)

我对 C 的技术知识不多(只是学习了相关的部分),我想知道它是如何工作的。大小写不同,#define 行中有“__”前体。这是如何工作的?

【问题讨论】:

  • 它“不起作用”。如您所述,大小写和__ 不同。必须在某处定义一个实际的kavl_insert
  • 我看了看,发现了类似的东西。 __KAVL_INSERT 和 kavl_insert 之间有联系吗?
  • 不在您显示的代码中。也许kavl_insert_nei2(即kavl_insert_##sufsuf = nei2)调用它,也许不是。
  • 这里的“__scope __type”是什么意思?我在想如果有联系它就会在那里,但似乎没有
  • __scope__type 是宏的参数。从名字来看,__type 是返回类型(这里* 总是添加到类型中),但要确定你必须看看宏是如何调用的。

标签: c macros


【解决方案1】:

第一个__KAVL_INSERT 宏用于声明所有以相同前缀(kavl_insert_)开头并以指定后缀(参数suf)结尾的函数。

所以,当你看到这个时:

__KAVL_INSERT(foo, static, int, null, null)

预处理器会将其替换为具有适当名称、范围和参数类型的函数:

static int *kavl_insert_foo(int **root_, int *x, unsigned *cnt_) { \
    /* actual function body ... */ \
    /* with lots of trailing backshashes ... */ \
    /* because it's the only way to create ... */ \
    /* a multiline macro in C */ \
}

另一方面,小写的kavl_insert 宏:

kavl_insert(foo, &something, &whatever, 0);

只是扩展为实际的函数调用,即相当于调用上面定义的函数:

kavl_insert_foo(&something, &whatever, 0);

这种宏背后的想法通常是使用预处理器在 C 中创建一个通用类型安全的数据结构,例如各种通用数据结构的 klib 库。

【讨论】:

  • 感谢您的回复!澄清一下,预处理器如何知道将__KAVL_INSERT 替换为具有适当名称、范围和参数类型的函数?是因为下面的行,__scope __type *kavl_insert__##suf(...)?另外,你写的,static int *kavl_insert_foo(int **root_, ...),是不是这里的更普遍的形式,__scope __type *kavl_insert_##suf(__type **root_,...)?最后,澄清一下,代码中调用的是kavl_insert(foo...),它本身扩展为上面定义的_KAVL_INSERTstatic int *kavl_insert_foo(...)
  • @Chris: double hash sign (##) 告诉预处理器连接两个字符串,所以kavl_insert_##suf 的意思是“将传递给宏的suf 参数附加到kavl_insert_ 字符串。如果你在 gcc 中使用-E 参数(你可以用godbolt online compiler 检查这个),你可以看到预处理文件在编译阶段之前的样子。链接网页中的左窗格显示源代码,右窗格显示带有-E 的 gcc 8.2 的最终结果。
  • 如果您决定编写自己的通用数据结构,klib 做这一切的原因可能会变得显而易见。例如,类向量数据结构在 C 中非常有用(一个包含指向数组的指针、其容量和当前长度的结构,并根据需要自动增加容量)。如果您想传递int 向量列表、char 向量或任何自定义结构的向量,则必须一遍又一遍地复制所有函数(或使用char*/void*然后围绕代码进行强制转换,这使得代码容易出现运行时错误)。
  • kavl_insert(foo, ...) 是否连接到__KAVL_INSERT(foo,...),如果连接,如何连接?抱歉,如果我遗漏了什么。在我看来 KAVL_INSERT(foo...) 声明以 kavl_insert_ 开头的函数会排除 `kavl_insert',因为它缺少最后一个下划线。
  • 如果你想调用x_b,你需要在某处使用_X(b)声明,是的。如果您需要特定类型的 x_type 函数,您希望使用 _X 宏通过预处理器发出其整个源代码。我相信充分理解这个概念的最好方法是尝试自己设计一个动态数组之类的东西。浏览this quick example,一旦你明白发生了什么,想想你将如何重用这段代码,这样你就可以获得float的列表或struct something的列表,比如this
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-04
  • 2019-01-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多