在编译时复制C/C++函数

Duplicating C/C++ functions at compile time

本文关键字:C++ 函数 复制 编译      更新时间:2023-10-16

如果我有一个函数A(),我有兴趣找到一种方便的方法来创建一个与A()功能完全相同、只是名称不同的函数B()。新功能将一次性使用。其目的是在某种程度上原始的采样探查器中区分对同一函数的调用,并且重复的函数只能在该上下文中使用。也就是说,它永远不会触及生产代码,只能用于修补。

首先猜测的是一个宏,它声明了一个名为B的函数,并在其中创建了一个对A()的内联调用。这里的问题是,我不知道GCC中有什么方法可以强制任意函数调用内联;似乎所有的内联选项都是用于函数声明而不是调用的。

使用模板可能有一些深奥的方法,或者可能通过欺骗编译器进行内联。我不确定这是否可能。有什么想法吗?不幸的是,新的C++标准不可用,如果它能带来不同的话。

使用模板

template<int x>
void A()
{
    // ..
}
int main()
{
    A<0>();
    A<1>();
    return 0;
}

更新

编译器可能太聪明,并且只为A<0>和A<1> 。至少Visual C++2010在发布模式下可以做到这一点。为了防止这种情况,只需在日志或断言中的函数模板主体中使用模板参数。例如,

#include <iostream>
template<int x>
void A()
{
    ::std::cout << x << std::endl;
    // ..
}
int main()
{
    A<0>();
    A<1>();
    auto v0 = A<0>;
    auto v1 = A<1>;
    ::std::cout << v0 << std::endl;
    ::std::cout << v1 << std::endl;
    ::std::cout << (v0 == v1) << std::endl;
    return 0;
}

这使用模板工作:

#include <iostream>                                                             
template<typename T>
void foo() {
    static int x = 0;
    std::cout << &x << std::endl;
}
int main(int argc, char **argv) {
    foo<int>();
    foo<float>();
    return 0;
}

如果执行该操作,您将看到打印出两个不同的值,反映了编译器为两个调用生成的代码,即使模板参数未使用。对象文件上的nm证实了这一点。

如果这是一次性调试破解,那么为什么不呢:

#define A_CONTENT 
    ... // whatever
void A()
{
    A_CONTENT
}
void B()
{
    A_CONTENT
}
...
A();  // Call to A
B();  // Call to B  

宏通常很可怕,但我们在这里谈论的不是生产代码,所以谁在乎呢?

我自己也经历过这条路,简单的答案是,即使你让编译器发出一个函数的两个相同副本,优化链接器也会注意到它们是相同的,并将它们重新组合到一个实现中。(如果你关闭了链接器中的优化,那么你的配置文件也无效)。

在采样探查器的上下文中,我发现更简单的方法是为函数制作两个小包装器:

void Func() { .... }
_declspec(noinline) 
void A_Func( return Func(); }
void B_Func( return Func(); }
void C_Func( return Func(); }

然后,当您的探查器对调用堆栈进行采样时,您将能够以一种非常简单的方式区分该函数的不同调用位置。。

您可以始终定义一个宏,例如在Chromium中,我们执行以下操作来重用代码:

#define CHROMEG_CALLBACK_1(CLASS, RETURN, METHOD, SENDER, ARG1)     
  static RETURN METHOD ## Thunk(SENDER sender, ARG1 one,            
                                gpointer userdata) {                
    return reinterpret_cast<CLASS*>(userdata)->METHOD(sender, one); 
  }                                                                 
                                                                    
  virtual RETURN METHOD(SENDER, ARG1);

我们称它们为:

 CHROMEGTK_CALLBACK_1(PageActionViewGtk, gboolean, OnExposeEvent, GdkEventExpose*);
 CHROMEGTK_CALLBACK_1(PageActionViewGtk, gboolean, OnButtonPressed, GdkEventButton*);

你可以做一些类似的事情来做你想做的事。上面的例子展示了我们使用两种不同的实现,但有一个共同的代码库。用于GTK回调。

目前还不清楚你真正想做什么,但一个非常糟糕的解决方案是将a的主体声明为宏,然后你可以在任何你喜欢的函数中"内联"这个宏。

此外,宏也是邪恶的。除非你真的必须使用,否则永远不要使用它们。

为什么如此关心内联?如果您创建了一个包装函数,那么编译器很有可能会内联它。至少,你不太可能构建一个函数框架。

C++11也可以让你做到这一点:

void A() {
    ...
}
...
auto B = [] () -> void { A(); };

现在,您可以在语法上使用B,就好像它是一个包装a的函数一样。