我有以下代码。有一个函数需要两个 int32。然后我获取一个指向它的指针并将其转换为一个需要三个 int8 的函数并调用它。我预计会出现运行时错误,但程序运行良好。为什么这甚至可能?
main.cpp:
#include <iostream>
using namespace std;
void f(int32_t a, int32_t b) {
cout << a << " " << b << endl;
}
int main() {
cout << typeid(&f).name() << endl;
auto g = reinterpret_cast<void(*)(int8_t, int8_t, int8_t)>(&f);
cout << typeid(g).name() << endl;
g(10, 20, 30);
return 0;
}
输出:
PFviiE
PFvaaaE
10 20
正如我所见,第一个函数的签名需要两个整数,第二个函数需要三个字符。 Char 比 int 小,我想知道为什么 a 和 b 仍然等于 10 和 20。
最佳答案
正如其他人所指出的那样,这是未定义的行为,因此原则上可能发生的事情都是错误的。但是假设您使用的是 x86 机器,那么对于您为什么会看到这个问题有一个合理的解释。
在 x86 上,g++ 编译器并不总是通过将参数压入堆栈来传递参数。相反,它将前几个参数存储到寄存器中。如果我们反汇编 f
函数,请注意前几条指令将参数移出寄存器并显式移入堆栈:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi # <--- Here
mov DWORD PTR [rbp-8], esi # <--- Here
# (many lines skipped)
同样,请注意调用是如何在 main
中生成的。参数被放入这些寄存器中:
mov rax, QWORD PTR [rbp-8]
mov edx, 30 # <--- Here
mov esi, 20 # <--- Here
mov edi, 10 # <--- Here
call rax
由于整个寄存器都被用来保存参数,所以参数的大小在这里并不重要。
此外,由于这些参数是通过寄存器传递的,因此无需担心以不正确的方式调整堆栈大小。一些调用约定(cdecl
)让调用者进行清理,而另一些(stdcall
)要求被调用者进行清理。然而,这两者都不重要,因为堆栈没有被触及。
关于c++ - 为什么这个函数调用在通过类型转换的函数指针调用它后表现得很好?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56727050/