【问题标题】:Define a function which function name is masked by a macro定义一个函数,其函数名被宏屏蔽
【发布时间】:2019-01-19 02:11:47
【问题描述】:

有时,用同名的宏来屏蔽函数会很方便。 在这个人为的示例中,宏可以在调用 function() 时插入额外的验证。

int function(int i);

#define function(i)  ( assert(i>0), function(i) )

其他更常见的用法使宏转换为更快的代码,例如直接表入口点,以提高性能。 在所有情况下,目标都是在库中保留同名符号,以确保它存在并且可以由另一个程序从库中调用,而不需要 *.h(通常来自与 @987654324 不同的语言的包装器@)。

在相应的单元中,定义function() 现在有一个问题:function 现在是一个宏。所以int function(int i) { ... } 将被扩展,并且非常失败。

解决这个问题的方法很简单:在定义前#undef function,然后实现函数。

一切都很好,除了...

如果在同一单元的后面部分中,另一个函数调用function(),它现在将直接调用符号,而不是*.h 中的宏,从而失去宏提供的额外功能,这可能是不可取的。

gcc 中提供了另一个解决此问题的方法,使用编译器扩展#pragma push_macro。 它可以工作,但非常难看,在函数定义周围添加了至少 3 行,这对可读性没有帮助。这还不包括可移植性的恶作剧,这会增加更多的复杂性。

然而,用同名宏屏蔽函数并不是什么新鲜事。我很确定我早在 C90 标准库中就已经阅读过建议这种设计的书籍。

我怀疑这个问题可能存在更好的解决方案。 有什么想法吗?

【问题讨论】:

    标签: c gcc macros masking


    【解决方案1】:

    类似函数的宏的另一种技巧是在定义中的声明符周围放置多余的括号:

    int (function)(int i) {
        ...
    }
    

    这可以防止类函数宏匹配和扩展,但对定义没有影响。

    【讨论】:

    • 请注意,对于至少一些类似函数的宏,您可以通过编写:int (function)(int i) { return function(i); } 来确保宏和函数的一致性——名称周围的括号停止被宏扩展,并且名称周围没有括号意味着宏在 return 语句中展开。当然,如果没有返回值,就省略return关键字——void (alternative)(int i, double d) { alternative(i, d); }。此技术不适用于所有宏,但适用于许多类似函数的宏。
    【解决方案2】:

    有时,用同名的宏屏蔽函数会很方便。

    类似宏的包装函数,比如

    static inline int actual_function(int i)
    {
        /* extra stuff */
        return function(i);
    }
    

    在编译时是一个更好的选择,因为最小意外原则。 (例如,您让开发人员感到惊讶,他们会做一些愚蠢的事情。因此,惊喜越少,他们可能犯的愚蠢错误就越少。)

    不幸的是,这对更名没有帮助。我个人不在乎,因为无论如何,重命名只是find . -name '*.[ch]' -exec sed -e 's|\bOLDNAME\b|NEWNAME|g' -i '{}' ';' 的一个快捷方式。


    对于动态链接的符号,我们可以在运行时用我们自己的包装或插入它们。这是高度特定于操作系统和工具链的。

    在 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() 等,所以它只是多几行代码。)

    更简单的方法是告诉 GCC 为我们执行符号魔法,方法是在编译和链接二进制文件时为其提供选项 -Wl,-wrap,malloc。那么,

    #include <stdlib.h>
    
    void *__real_malloc(size_t);
    
    void *__wrap_malloc(size_t size)
    {
        /* extra stuff */
        return __real_malloc(size);
    }
    

    这一次,插入/包装是在符号表级别完成的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-11
      • 1970-01-01
      • 2013-02-24
      • 1970-01-01
      • 2015-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多