Reputation: 233
Consider the following code:
#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;
}
As you can see, a type (test_func_t) is defined as a function with 3 int arguments. A function pointer (func) is assigned with a pointer to "print_integer", which receives only 1 argument, and the function pointer is then called with 3 arguments (5, 7, 9).
This code works and produces "num: 5" output.
gdb disas output (Intel syntax)
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>
As you can see, the rest of the arguments ([ebp+0x12] and [ebp+0x16]) are simply not used.
My questions:
Example of such use: node.js's NODE_MODULE registers a function whose type has 3 arguments [exports, module, priv]. It is called with those 3 but formal examples show registering of a function with 1 or 2 arguments.
Upvotes: 3
Views: 1227
Reputation: 25286
Thus will work on all platforms that use the __cdecl calling convention. This calling convention pushes the arguments right-to-left onto the stack and then calls the function. A function requiring less parameters than pushed will thus simply not access the parameters more to the right, which were pushed earlier and are higher on the stack.
// 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: -------------+
Upvotes: 3
Reputation: 12777
Quoting from the C++11 standard expr.reinterpret.cast 6:
A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined.
So, I'd say that it is not safe in general. It is undefined behaviour. That said, I don't know how other implementation of C++ behave in this situation.
Upvotes: 12