有时,用同名宏掩盖函数可能很方便。
在这个人为的示例中,宏使调用function()
时可以插入其他验证。
int function(int i);
#define function(i) ( assert(i>0), function(i) )
其他更常见的用法使宏转换为更快的代码,例如直接表入口点,从而提高了性能。
在所有情况下,目标都是在库中保留相同名称的符号,以确保它存在并且可以由另一个程序从库中调用,而无需使用*.h
(通常从包装器以与C
。
在相应的单元中,现在定义function()
会遇到问题:function
现在是宏。因此int function(int i) { ... }
将会扩展,并且会严重失败。
此问题的解决方案很简单:只需在定义前#undef function
,然后实现该功能。
一切都很好,除了...
如果在同一单元的后续部分中,另一个函数调用function()
,则它将直接调用符号,而不是*.h
中的宏,从而失去了提供的额外功能宏,这可能是不可取的。
gcc
中使用编译器扩展名#pragma push_macro
提供了解决此问题的另一种方法。
它可以工作,但是很难看,在函数定义周围至少添加三行,这不利于可读性。这还不包括可移植性的恶作剧,这将增加更多的复杂性。
但是,用相同名称的宏屏蔽函数并不是什么新鲜事。我敢肯定,我早在C90标准库中就读过建议这种设计的书籍。
我怀疑可能存在针对此问题的更好解决方案。 有什么想法吗?
答案 0 :(得分:4)
类似函数的宏的另一种技巧是在定义的声明符周围放置多余的括号:
int (function)(int i) {
...
}
这防止了类似于函数的宏的匹配和扩展,但对定义没有影响。
答案 1 :(得分:0)
有时候,用同名宏掩盖函数可能很方便。
类似宏的包装函数,如
static inline int actual_function(int i)
{
/* extra stuff */
return function(i);
}
由于最小惊喜原则,在编译时是一个更好的选择。 (例如,使开发人员感到惊讶的是,他们做愚蠢的事情。因此,惊喜越少,他们可能犯的愚蠢错误就越少。)
不幸的是,这对名称更改没有帮助。我个人并不在乎,因为无论如何,重命名仅需快速find . -name '*.[ch]' -exec sed -e 's|\bOLDNAME\b|NEWNAME|g' -i '{}' ';'
。
对于动态链接的符号,我们可以在运行时使用我们自己的符号将它们包装或插入。这是高度特定于OS和工具链的。
在Linux中,有两个选项:使用动态链接器工具(dlsym()
)或--wrap
链接器选项。它仅适用于动态链接的符号,但通常,至少标准库是动态链接的。
简而言之,如果您想将动态链接的malloc()
替换为自己的函数,则可以使用
#define _GNU_SOURCE
#include <stdlib.h>
#include <dlfcn.h>
static void *(*real_malloc)(size_t) = NULL;
void *malloc(size_t size)
{
if (!real_malloc)
real_malloc = dlsym(RTLD_NEXT, "malloc");
/* extra stuff */
return real_malloc(size);
}
如果您想以线程安全的方式执行上述操作,它将变得更加复杂。 (我使用__atomic_load_n()
等,因此仅多了几行代码。)
更简单的方法是通过在编译和链接二进制文件时为其提供选项-Wl,-wrap,malloc
来告诉GCC为我们做符号魔术。然后,
#include <stdlib.h>
void *__real_malloc(size_t);
void *__wrap_malloc(size_t size)
{
/* extra stuff */
return __real_malloc(size);
}
这次,插入/包装是在符号表级别完成的。