dri*_*ker 52 c pointers function-pointers
我在C中读到了函数指针.每个人都说这将使我的程序运行缓慢.这是真的吗?
我制作了一个程序来检查它.我在两个案例中得到了相同的结果.(衡量时间.)
那么,使用函数指针是不是很糟糕?提前致谢.
为了回应一些人.在循环中我比较的时候我说"运行缓慢".像这样:
int end = 1000;
int i = 0;
while (i < end) {
 fp = func;
 fp ();
}
执行此操作时,如果执行此操作,我会得到相同的时间.
while (i < end) {
 func ();
}
所以我认为函数指针没有时间差异,也没有像许多人所说的那样使程序运行缓慢.
AnT*_*AnT 82
您可以看到,在从性能角度来看实际上很重要的情况下,例如在一个周期中多次重复调用该函数,性能可能根本不同.
这对于人们来说可能听起来很奇怪,他们习惯于将C代码视为由抽象的C机器执行的,其机器语言与C语言本身密切相关.在这种情况下,"默认情况下"对函数的间接调用确实比直接调用慢,因为它正式涉及额外的内存访问以确定调用的目标.
但是,在现实生活中,代码由真实机器执行,并由优化编译器编译,该编译器非常了解底层机器架构,这有助于它为该特定机器生成最佳代码.在许多平台上,可能会发现从循环执行函数调用的最有效方法实际上会导致直接和间接调用的相同代码,从而导致两者的相同性能.
例如,考虑x86平台.如果我们"直接"将直接和间接调用转换为机器代码,我们可能最终得到类似的东西
// Direct call
do-it-many-times
  call 0x12345678
// Indirect call
do-it-many-times
  call dword ptr [0x67890ABC]
前者在机器指令中使用立即操作数,并且通常比后者更快,后者必须从某个独立的存储器位置读取数据.
在这一点上,让我们记住,x86架构实际上还有一种方法可以为call指令提供操作数.它在寄存器中提供目标地址.关于这种格式的一个非常重要的事情是它通常比上述两种都快.这对我们意味着什么?这意味着一个好的优化编译器必须并将利用这一事实.为了实现上述循环,编译器将尝试在两种情况下通过寄存器使用调用.如果成功,最终代码可能如下所示
// Direct call
mov eax, 0x12345678
do-it-many-times
  call eax
// Indirect call
mov eax, dword ptr [0x67890ABC]
do-it-many-times
  call eax
注意,现在重要的部分 - 循环体中的实际调用 - 在两种情况下都完全相同.毋庸置疑,性能几乎完全相同.
有人甚至说,但奇怪的是听起来,这个平台上直接调用(与立即数的调用call)是慢比只要间接调用中提供的操作数的间接调用寄存器(相存储在内存中).
当然,在一般情况下,整个事情并不那么容易.编译器必须处理有限的寄存器可用性,别名问题等.但是像你的例子中那样简单的情况(甚至在更复杂的情况下),上述优化将由一个好的编译器执行并将完全消除循环直接调用和循环间接调用之间的性能差异.当调用虚函数时,这种优化在C++中特别有效,因为在典型的实现中,所涉及的指针完全由编译器控制,使其充分了解别名图片和其他相关内容.
当然,总有一个问题是你的编译器是否足够智能以优化这样的事情......
Tyl*_*nry 25
我想当人们这么说时,他们指的是使用函数指针可能会阻止编译器优化(内联)和处理器优化(分支预测).但是,如果函数指针是一种有效的方法来完成你想要做的事情,那么任何其他方法都可能会有同样的缺点.
除非您的函数指针在性能关键应用程序或非常慢的嵌入式系统中用于紧密循环,否则无论如何差异都可以忽略不计.
很多人都提出了一些好的答案,但我仍然认为有一点被遗漏了.函数指针确实添加了一个额外的解引用,这使得它们几个周期变慢,这个数字可以根据不良的分支预测而增加(顺便提一下,它几乎与函数指针本身无关).另外,通过指针调用的函数不能内联.但人们缺少的是大多数人使用函数指针作为优化.
您将在c/c ++ API中找到函数指针的最常见位置是回调函数.这么多API这样做的原因是因为编写一个在事件发生时调用函数指针的系统比其他方法(如消息传递)更有效.我个人也使用函数指针作为更复杂的输入处理系统的一部分,其中键盘上的每个键都有一个通过跳转表映射到它的函数指针.这允许我从输入系统中删除任何分支或逻辑,只是处理进入的按键.
每个人都说会让我的程序运行缓慢.这是真的吗?
这种说法很可能是错误的.首先,如果使用函数指针的替代方法是这样的
if (condition1) {
        func1();
} else if (condition2)
        func2();
} else if (condition3)
        func3();
} else {
        func4();
}
这是最有可能相对比只用一个单一的函数指针慢得多.虽然通过指针调用函数确实有一些(通常是可忽略的)开销,但通常不是与比较相关的直接函数调用与通过指针调用的差异.
其次,在没有任何测量的情况下,永远不要优化性能.知道瓶颈在哪里是非常困难的(阅读不可能)知道,有时这可能非常不直观(例如Linux内核开发人员已经开始inline从函数中删除关键字,因为它实际上损害了性能).
通过函数指针调用的函数是有点不是静态函数调用慢,因为前者调用包括一个额外的指针废弃.但是AFAIK在大多数现代机器上这种差异可以忽略不计(除了一些资源非常有限的特殊平台).
使用函数指针是因为它们可以使程序更简单,更清晰,更易于维护(当然,正确使用时).这足以弥补可能非常小的速度差异.
使用函数指针比调用函数要慢,因为它是另一层间接.(需要取消引用指针以获取函数的内存地址).虽然速度较慢,但与程序可能执行的其他操作(读取文件,写入控制台)相比,它可以忽略不计.
如果你需要使用函数指针,请使用它们,因为任何试图做同样事情但避免使用它们的东西使用函数指针会更慢,更难维护.
早期回复中有很多好处.
但是看看C qsort比较函数.因为比较函数不能内联并且需要遵循基于标准堆栈的调用约定,所以对于整数键,排序的总运行时间可以比一个直接的相同代码慢一个数量级(更确切地说是3-10x),内联,电话.
典型的内联比较将是一系列简单的CMP和可能的CMOV/SET指令.函数调用还会产生CALL的开销,设置堆栈帧,进行比较,拆除堆栈帧并返回结果.注意,由于CPU流水线长度和虚拟寄存器,堆栈操作可能导致流水线停顿.例如,如果在上次修改的eax已经完成执行的指令之前需要说eax的值(在最新的处理器上通常需要大约12个时钟周期).除非CPU无法执行其他指令以等待,否则将发生管道停顿.