通过指针和名称将一个函数传递给另一个函数

Pass function to another function by pointer and by name

本文关键字:函数 一个 另一个 指针      更新时间:2023-10-16

我正在学习函数指针,这个例子来自wiki:

int add(int first, int second)
{
    return first + second;
}
int subtract(int first, int second)
{
    return first - second;
}
int operation(int first, int second, int (*functocall)(int, int))
{
    return (*functocall)(first, second);
}
int main()
{
    int  a, b;
    int  (*plus)(int, int) = add;
    a = operation(7, 5, plus);
    b = operation(20, a, subtract);
    cout << "a = " << a << " and b = " << b << endl;
    return 0;
}

正如我所看到的,plus是一个指向函数add的指针,它被传递给函数operation。很明显。那么subtract呢?

为什么不使用指针?两种方法的区别是什么?是c++特异性的吗?

在c++中,函数可以自动转换为函数指针,所以下面是等价的:

b = operation(20, a, subtract);
b = operation(20, a, &subtract);

由于&substract具有正确的类型(int (*)(int, int)),因此代码按预期编译并工作。

是c++特有的吗?

不能真正回答这个问题,因为可能有其他语言允许这样做。

为什么substract不使用指针?

实际上,所有函数名都是对应活动作用域中的指针。这里,在您的代码中,当您定义函数substract()时,add()

还定义了两个名为substractadd的变量,它们的类型是函数指针:int (*)(int, int)。因此,您可以声明一个新的函数指针plus,并将add赋值给它。这三个变量都是指针。

下面是clang++生成的汇编代码,可以给你一个详细的解释。我已经删除了所有不相关的代码。函数名读起来有点难看,这是由于c++的命名混乱,你可以忽略大写字母和数字,以方便地理解名称。

函数add():

    .text
    .globl  _Z3addii
    .align  16, 0x90
    .type   _Z3addii,@function
_Z3addii:                               # @_Z3addii
    .cfi_startproc
# BB#0:                                 # %entry
    movl    %edi, -4(%rsp)
    movl    %esi, -8(%rsp)
    movl    -4(%rsp), %esi
    addl    -8(%rsp), %esi
    movl    %esi, %eax
    ret
.Ltmp6:
    .size   _Z3addii, .Ltmp6-_Z3addii
    .cfi_endproc

函数substract:

    .globl  _Z8subtractii
    .align  16, 0x90
    .type   _Z8subtractii,@function
_Z8subtractii:                          # @_Z8subtractii
    .cfi_startproc
# BB#0:                                 # %entry
    movl    %edi, -4(%rsp)
    movl    %esi, -8(%rsp)
    movl    -4(%rsp), %esi
    subl    -8(%rsp), %esi
    movl    %esi, %eax
    ret
.Ltmp7:
    .size   _Z8subtractii, .Ltmp7-_Z8subtractii
    .cfi_endproc

_Z3addii_Z8subtractii标签给出了两个函数的起始点,这也是一个指向函数起始点的地址。

我在代码中添加了一些注释,以显示函数指针是如何工作的,从###开始。

函数main:

    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %entry
    pushq   %rbp
.Ltmp16:
    .cfi_def_cfa_offset 16
.Ltmp17:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Ltmp18:
    .cfi_def_cfa_register %rbp
    subq    $32, %rsp
    movl    $7, %edi
    movl    $5, %esi
    leaq    _Z3addii, %rax  ### Here, the assembly just load the label of _Z3addii, not a plus related variable, so in fact they are  the same type.
    movl    $0, -4(%rbp)
    movq    %rax, -24(%rbp)
    movq    -24(%rbp), %rdx  ### move the value of the function pointer to the rdx register.
    callq   _Z9operationiiPFiiiE
    movl    $20, %edi
    leaq    _Z8subtractii, %rdx  ### Here, just load the label -f _Z8subsractii, which is the value of the function pointer substract. move it directly to rdx register.
    movl    %eax, -8(%rbp)
    movl    -8(%rbp), %esi
    callq   _Z9operationiiPFiiiE
    leaq    _ZSt4cout, %rdi
    leaq    .L.str, %rsi
    movl    %eax, -12(%rbp)
    callq   _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    -8(%rbp), %esi
    movq    %rax, %rdi
    callq   _ZNSolsEi
    leaq    .L.str1, %rsi
    movq    %rax, %rdi
    callq   _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    movl    -12(%rbp), %esi
    movq    %rax, %rdi
    callq   _ZNSolsEi
    leaq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %rsi
    movq    %rax, %rdi
    callq   _ZNSolsEPFRSoS_E
    movl    $0, %ecx
    movq    %rax, -32(%rbp)         # 8-byte Spill
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
.Ltmp19:
    .size   main, .Ltmp19-main
    .cfi_endproc

在上面的main函数集合中,我们看到所有的函数指针值都被移到了寄存器rdx中。下面的操作函数:

函数operation():

    .globl  _Z9operationiiPFiiiE
    .align  16, 0x90
    .type   _Z9operationiiPFiiiE,@function
_Z9operationiiPFiiiE:                   # @_Z9operationiiPFiiiE
    .cfi_startproc
# BB#0:                                 # %entry
    pushq   %rbp
.Ltmp10:
    .cfi_def_cfa_offset 16
.Ltmp11:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Ltmp12:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movq    %rdx, -16(%rbp)
    movq    -16(%rbp), %rdx
    movl    -4(%rbp), %edi
    movl    -8(%rbp), %esi
    callq   *%rdx  ### directly jump to the address, which is the value of the rdx register.
    addq    $16, %rsp
    popq    %rbp
    ret
.Ltmp13:
    .size   _Z9operationiiPFiiiE, .Ltmp13-_Z9operationiiPFiiiE
    .cfi_endproc

那么,从程序集可以看出,代码中的substractaddplus变量都是指针,都来自于函数起始点的标签。

这两种方法的区别是什么?

由于plusaddsubstract都是相同类型的函数指针,就像Luchian Grigore所说的那样,它们是相同的。

从上面的汇编代码中,我们也可以发现,这两个方法绝对是同一个方法调用,没有任何区别。

是否指定了C++ ?

C家族语言

和其他一些派生语言,如obj-c,直接支持函数指针。

Java语言

在Java中,有一个叫做函数指针的概念,但是一个class (implemnts一个接口)可以达到和函数指针一样的目的。

例如

:你可以先定义一个接口:

interface StringFunction {
  int function(String param);
}

,然后定义一个可以接受实现接口的对象的函数:

public void takingMethod(StringFunction sf) {
   //stuff
   int output = sf.function(input);
   // more stuff
}

然后你可以定义不同的类implements接口StringFunction,并使用它作为takingMethod()的参数

Python

在Python中,函数名只是变量的一种类型,你可以像下面这样直接使用它:

def plus_1(x):
    return x + 1
def minus_1(x):
    return x - 1
func_map = {'+' : plus_1, '-' : minus_1}
func_map['+'](3)  # returns plus_1(3) ==> 4
func_map['-'](3)  # returns minus_1(3) ==> 2
Ruby

Ruby也有一些类似的方法:

相关文章: