C++中的函数地址字面量在哪里?

6

更新:在进一步阅读后,我真正想要的是保证早期绑定(这应该被翻译为非虚函数和非PIC代码的立即调用),这可以通过将(成员)函数作为模板参数传递来实现。我的问题是gcc < 4.5和icc 11.1可以为成员函数指针模板参数调用生成一些奇怪的指令。据我所知,gcc >= 4.5和vs2008可以很好地处理这些模板参数调用。

首先,也许“字面值”不是这个概念的正确术语,但这是我能想到的最接近的术语(不是指函数作为一等公民的文字意义)。

这个想法是,当你进行传统的函数调用时,它编译成类似这样的东西:

callq <immediate address>

但是如果你使用函数指针进行函数调用,它会被编译成类似这样的代码:

mov    <memory location>,%rax
callq  *%rax

这些都很好。但是,如果我正在编写一个需要特定参数列表的某种回调函数的模板库,并且期望该库的用户在编译时知道要调用哪个函数怎么办?那么我希望编写我的模板以接受函数字面量作为模板参数。因此,与之类似

template <int int_literal> 
struct my_template {...};` 

我想写

template <func_literal_t func_literal>
struct my_template {...};

我希望在我的模板中调用func_literal函数时,能够编译成callq <immediate address>。是否有C++的相关功能或者解决方法来实现这个效果?如果没有,为什么没有(例如会产生灾难性的副作用)?C++0x或其他语言呢?


这个主题非常有用,因为它允许在运行时交换不同的功能。例如,DOS API使用ID映射到函数。这使得DOS功能可以在最小影响可执行文件的情况下进行更改。只需更改查询表中的内容(函数地址)即可。 - Thomas Matthews
使用函数对象与模板可以在编译时实现。STL 的大部分工作都是这样的,或者你的问题中有什么需要注意的地方吗? - pmr
@pmr 的注意事项是,解决方案不应对用户的代码产生任何影响;如果他们没有使用函数对象,我不想强制他们使用函数对象。 - academicRobot
@Thomas Matthews 我觉得你误解了我的意思,调用应该编译为 callq <立即地址>,在运行时不应该有任何灵活性。 - academicRobot
@academicRobot:如果他们自己不使用函数对象,那么像boost::function(&foo)这样的技巧可以将函数指针转换为函数对象。 - MSalters
@MSalters 以这种方式构造函数对象会导致函数调用通过函数指针进行,这正是这个问题试图避免的。 - academicRobot
7个回答

3

如果您在模板中使用函数指针类型并使用固定函数进行实例化,则编译器应该对该函数指针调用使用直接调用。


+1 刚试了一下,效果很好。那么对于来自任意类的成员函数(不考虑虚函数)的接受呢?你的答案给了我一些想法... - academicRobot

1
#include <iostream>                                                             

template<void F()>                                                              
struct CALLER                                                                   
{                                                                               
  static void do_call()                                                         
  {                                                                             
    std::cout << __PRETTY_FUNCTION__ << std::endl;                              
    F();                                                                        
  };                                                                            
};                                                                              

void f()                                                                        
{                                                                               
  std::cout << __PRETTY_FUNCTION__ << std::endl;                                
}                                                                               

int main()                                                                      
{                                                                               
  CALLER<f>::do_call();                                                         
  return(0);                                                                    
}                                                                               

1

第一个链接仍然使用指针调用,尽管它们比成员函数调用更快。我还不知道第二个链接,会去了解一下... - academicRobot
之前没有看到你打算阅读第二个链接。我刚刚试了一下,但是没有成功。实际上,这有点奇怪。有一个内部结构嵌入了调用(在立即模式下),但是它本身是通过函数指针调用的。我认为这是为了在委托之间获得互操作性,而这在这里并不是必需的。 - academicRobot

0

至少如果我正确理解了你的问题,这是微不足道的:

template <class func>
struct whatever { 
    operator()() { 
        func();
    }
};

func();的调用通常会直接调用func(),或者如果func()很小,则其代码通常会被内联生成。如果您想提高其内联生成的机会,通常要将其编写为函数对象(重载operator()的类),而不是普通函数。作为普通函数,您更有可能调用实际函数(但它将是直接调用,而不是通过指针调用)。

编辑:我不确定我当时在想什么,但你是完全正确的:这只适用于函数对象,而不是实际函数。我的道歉。


使用函数对象时效果很好。但对于函数来说无法编译通过:int echo(int i){return i;} int main(void){whatever whateverObj;return 1;}会出现"error: expected a type, got ‘echo’"的错误提示。 - academicRobot
这是我尝试的第一件事情之一,所以你不会从我这里得到任何批评 :) - academicRobot
这是正确的解决方案。请参见std::less<T>,它使用完全相同的模式,出于同样的原因。直接调用是为什么std::sort胜过std::qsort的原因。 - MSalters
@MSalters 不行(请参阅对pmr的评论) - academicRobot

0

我想分享一下我自己的标准函数解决方案,它是从其他答案扩展而来的。这使用可变参数作为简写。将其作为一组N元模板完成并不难(只是繁琐)。有趣的是,N元模板对于此模式更加灵活,因为不再需要嵌套结构体。可以使用g++ -std=c++0x编译。

template <typename F>
struct caller;

template <class R, class ... A>
struct caller<R(A ...)>{
   template <R F(A ...)>
   struct func{
      R operator()(A ... args){
         return F(args ...);
      }
   };
};

这个函数可以像这样调用和执行:

int echoFunc(int i) {std::cout << "echo " << i << std::endl; return i;}
...
caller<int(int)>::func<echoFunc> f;
f(1);

没有-O2编译会生成两个嵌套的立即函数调用,使用-O2时,对f(1)的调用会减少为对echoFunc的立即调用。

对于成员函数而言,gcc>=4.5和vs2008也能正常工作。


0

还可以将函数引用传递给模板。这在最新的clang(3.2)中编译,并像预期的那样打印“Hello World!”:

template<void(& f)()> struct test {
    void operator()() {
        f();
    }
};
void foo() {
    std::cout << "Hello World!\n";
}
test<foo> bar;
int main() {
    bar();
}

我不确定这是否与使用函数指针相比真的有所不同。


0
在 C++ 语言中,编译器不会将函数名折叠到可执行文件中,它们会永远丢失。您可以制作一个函数名称对应函数地址的表格。由于 C 和 C++ 非常注重类型信息,因此可能更容易使用汇编语言来声明。在高级语言中,您需要为每种不同的函数指针拥有一个表格。但在汇编中,这并不重要。虽然您可以使用带有 switch 的函数来返回函数指针或执行函数。
另一种选择是使用函数 ID(枚举)来对应函数地址。

我不想在运行时使用函数名,而是想在编译时使用函数名的代理。你提出的解决方案仍然在运行时使用函数指针。除非我误解了,请详细说明是否是这种情况。 - academicRobot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接