如何通过作为参数传递的函数指针调用函数?

How to call a function through a function pointer passed as an argument?

如何在汇编中的 f1 函数的第三个参数中调用传递的函数 (*f2)? 声明看起来像这样:

extern float f1(int v1, float v2, float (*f2)(int v3, float v4));

我想将 v1 传递给 v3,将 v2 传递给 v4,调用函数 f2,并且 return 值

f1:
    push rbp           
    mov rbp, rsp

    mov rdx, rdi ; v1 to v3
    mov xmm1, xmm0 ; v2 to v4
    call ??? 
    mov xmm0, xmm1

    mov rsp, rbp       
    pop rbp    
ret

我用什么代替问号?

汇编代码因使用的微控制器而异。

不完全是您要查找的内容,但遵循 Windows 具有英特尔核心 I7 的平台生成的汇编代码:-

C代码:-

extern float f1(int v1, float v2, float (*f2)(int v3, float v4))
{
    float a = 10.0;
    int b = 12;

    f2(b, a);
    return a+ 12.5;
}

汇编代码:-

_f1:
 pushl  %ebp
 movl   %esp, %ebp
 subl   , %esp
 movl   LC0, %eax

 movl   %eax, -12(%ebp)
 movl   , -16(%ebp)
 movl   -12(%ebp), %eax
 movl   %eax, 4(%esp)
 movl   -16(%ebp), %eax
 movl   %eax, (%esp)
 movl   16(%ebp), %eax
 call   *%eax
 fstp   %st(0)
 flds   -12(%ebp)
 flds   LC1
 faddp  %st, %st(1)
 leave
ret

希望这会有所帮助。

没有"Abi64"这样的东西。由于您标记了 MASM 问题,我们可以猜测您正在使用 Windows 平台,并且显然“64”意味着这是 64 位代码,因此确实极大地缩小了可能性。但是,Windows 上仍然有两种常见的 64 位代码调用约定。其中之一是 __vectorcall,另一个是 Microsoft x64 调用约定(最初发明该约定是为了让所有其他调用约定都过时,但……没有)。

由于 Microsoft x64 调用约定是最常见的,在这种特殊情况下,使用 __vectorcall 不会改变任何东西,我假设您正在使用它。然后所需的代码变得非常简单。您需要做的就是从 f1 跳转到 f2,因为堆栈的设置是相同的。 f1的前两个参数是应该传给f2的两个参数,f2的return值是[=的return值16=]。因此:

f1:
    rex_jmp  r8    ; third parameter (pointer to f2) is passed in r8

这不仅写起来很简单,而且是大小和速度方面的最佳实现。
如果需要,您甚至可以预先修改 v1v2 参数,例如:

f1:
    inc      ecx        ; increment v1 (passed in ecx)

    ; multiply v2 (xmm1) by v1 (ecx)
    movd     xmm0, ecx
    cvtdq2ps xmm0, xmm0
    mulss    xmm1, xmm0

    rex_jmp  r8    ; third parameter (pointer to f2) is passed in r8

如果您想做一些更复杂的事情,它的工作方式如下:

f1:
    sub   rsp, 40     ; allocate the required space on the stack
    call  r8          ; call f2 through the pointer, passed in r8
    add   rsp, 40     ; clean up the stack
    ret

请注意,您不需要问题中显示的 prologue/epilogue 代码,但如果您选择包含它也不会造成任何伤害。

但是,您在问题中显示的示例代码中进行的参数改组是错误的!在 Microsoft x64 调用约定中,在 RCX、RDX、R8 和 R9 中,前四个整数参数从左到右传递到寄存器中。所有其他整数参数都在堆栈上传递。在 XMM0、XMM1、XMM2 和 XMM3 中,前最多四个浮点值也被传递到寄存器中,从左到右。其余的在堆栈上传递,连同对于寄存器来说太大的结构。

不过,奇怪的是插槽是 "fixed",所以总共只能使用 4 个寄存器参数,即使您混合使用整数和 fp 参数。因此:

╔═══════════╦══════════════════════════╗
║           ║           TYPE           ║
║ PARAMETER ╠═════════╦════════════════╣
║           ║ Integer ║ Floating-Point ║
╠═══════════╬═════════╬════════════════╣
║ First     ║   RCX   ║      XMM0      ║
╠═══════════╬═════════╬════════════════╣
║ Second    ║   RDX   ║      XMM1      ║
╠═══════════╬═════════╬════════════════╣
║ Third     ║   R8    ║      XMM2      ║
╠═══════════╬═════════╬════════════════╣
║ Fourth    ║   R9    ║      XMM3      ║
╠═══════════╬═════════╩════════════════╣
║ (rest)    ║         on stack         ║
╚═══════════╩══════════════════════════╝

第二个参数是第一个传递的浮点值并不重要。它不进入 XMM0,因为它是第一个浮点值,它进入 XMM1,因为它是第二个参数,因此在第二个 "slot" 中。 (这不同于 the x86-64 System V ABI,其中前 6 个整数参数进入寄存器,无论是否有 FP 参数)。

关于 Windows 参数传递的更多详细文档可用 here,包括示例。