调用一个函数指针,其指定函数的参数少于指针类型

时间:2015-07-17 15:38:30

标签: c++ c function-pointers calling-convention

请考虑以下代码:

#include <iostream>

typedef int (*test_func_t) (int, int, int);

int print_integer (int a)
{
    std::cout << "num: " << a << "\n";
    return a;
}

int main (int argc, char * argv[])
{
    test_func_t func = (test_func_t) &print_integer;
    std::cout << "calling with 3 parameters func(5,7,9)\n";
    func(5,7,9);
    return 0;
}

如您所见,类型(test_func_t)被定义为具有3个int参数的函数。函数指针(func)被赋予一个指向&#34; print_integer&#34;的指针,它只接收一个参数,然后用3个参数(5,7,9)调用函数指针。

此代码可以工作并生成&#34; num:5&#34;输出

gdb disas输出(英特尔语法)

disas main
...
   0x080486cb <+9>:     mov    DWORD PTR [esp+0x1c],0x804867d
...
   0x080486e0 <+37>:    mov    DWORD PTR [esp+0x8],0x9
   0x080486e8 <+45>:    mov    DWORD PTR [esp+0x4],0x7
   0x080486f0 <+53>:    mov    DWORD PTR [esp],0x5
   0x080486f7 <+60>:    mov    eax,DWORD PTR [esp+0x1c]
   0x080486fb <+64>:    call   eax

disas print_integer
   ...
   0x08048683 <+6>:     mov    DWORD PTR [esp+0x4],0x8048830
   0x0804868b <+14>:    mov    DWORD PTR [esp],0x8049ae0
   0x08048692 <+21>:    call   0x8048530 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@plt>
   0x08048697 <+26>:    mov    edx,DWORD PTR [ebp+0x8]
   0x0804869a <+29>:    mov    DWORD PTR [esp+0x4],edx
   0x0804869e <+33>:    mov    DWORD PTR [esp],eax
   0x080486a1 <+36>:    call   0x80484d0 <std::ostream::operator<<(int)@plt>

如您所见,其余参数([ebp + 0x12]和[ebp + 0x16])根本就没用过。

我的问题:

  1. 这似乎适用于带有__cdecl调用约定的Linux x86。它在其他架构和调用约定上是否安全?
  2. 是否有任何C / C ++标准允许/定义从期望较少参数的函数中分配函数指针的结果?
  3. 此类使用示例:node.js的NODE_MODULE注册一个函数,其type有3个参数[exports,module,priv]。它是called with those 3但正式示例显示了使用12参数注册函数。

2 个答案:

答案 0 :(得分:12)

引用C ++ 11标准 expr.reinterpret.cast 6

  

函数指针可以显式转换为不同类型的函数指针。打电话的效果   通过指向函数类型(8.3.5)的函数的函数,与定义中使用的类型不同   功能是未定义

所以,我会说它通常是安全。这是未定义的行为。也就是说,我不知道在这种情况下C ++的其他实现如何表现。

答案 1 :(得分:3)

因此将适用于使用__cdecl调用约定的所有平台。此调用约定将参数从右向左推入堆栈,然后调用该函数。因此,需要少于推送的参数的函数将不会更多地访问右边的参数,这些参数被更早地推送并且在堆栈中更高。

// func(5,7,9);
push 9
push 7
push 5
call func
---> func:
     push bp
     mov bp, sp
     mov ax, [bp+8]  ; get 5

// stack:
9
7
5                     [bp+8]
<return address>      [bp+4]
<value of old bp>        ^
    new bp: -------------+