我正在尝试分析提供给函数指针的额外或更少参数的变化,这些参数的大小不相同(参数更少或更多)。
考虑以下示例
#include <stdio.h>
#include <conio.h>
typedef void (*fp)(int a, int b, int c);
void function1(int a, int b) {
printf("\n Function1: a: %d, b: %d (%d)", a, b, __LINE__);
}
void function2(int a, int b, int c) {
printf("\n Function1: a: %d, b: %d, c: %d (%d)", a, b, c, __LINE__);
}
void function3(int a, int b, int c, int d) {
printf("\n Function1: a: %d, b: %d, c: %d, d: %d (%d)", a, b, c, d, __LINE__);
}
int main() {
fp fp1 = (fp)function1;
fp fp2 = (fp)function2;
fp fp3 = (fp)function3;
fp1(1, 2, 3);
fp2(4, 5, 6);
fp3(7, 8, 9);
getch();
return 0;
}
可以观察到fp1&amp; fp3采用与函数指针不同的参数大小(参数)。但是在执行程序时我仍然没有观察到任何未定义的行为。
该计划的输出是
Function1: a: 1, b: 2 (7)
Function1: a: 4, b: 5, c: 6 (11)
Function1: a: 7, b: 8, c: 9, d: 4200926 (15)
因此,在调用函数时,我们根据调用约定将地址和参数推送到堆栈,然后弹出。在上面的例子中,当我们将参数推送到堆栈并弹出它们时,我们会弹出少一个实际必须破坏返回地址的元素(在fp3的情况下也是如此),但所有代码都正常执行。
在这个stack overflow question中,函数参数没有不匹配。所以,我无法找到我的问题的答案。
从wikipedia开始,已经提到callee / caller需要像这样清理堆栈
Callee清理[编辑] 当被调用者清除堆栈中的参数时,需要在编译时知道堆栈需要调整多少字节。因此,这些调用约定与变量参数列表不兼容,例如, printf()函数。但是,它们可能更节省空间,因为不需要为每个调用生成展开堆栈所需的代码。 利用这些约定的函数很容易在ASM代码中识别,因为它们将在返回之前展开堆栈。 x86 ret指令允许一个可选的16位参数,该参数指定在返回调用者之前要展开的堆栈字节数。这样的代码如下所示:
ret 12
但我没有在上述程序的拆卸代码中观察到类似内容。 (粘贴在function1下面)
--------------------------------------------------------------------------------
18 int main() {
0x004013C7 push %ebp
0x004013C8 mov %esp,%ebp
0x004013CA and $0xfffffff0,%esp
0x004013CD sub $0x20,%esp
0x004013D0 call 0x401a00 <__main>
19 fp fp1 = (fp)function1;
0x004013D5 movl $0x401334,0x1c(%esp)
20 fp fp2 = (fp)function2;
0x004013DD movl $0x40135e,0x18(%esp)
21 fp fp3 = (fp)function3;
0x004013E5 movl $0x40138f,0x14(%esp)
23 fp1(1, 2, 3);
0x004013ED movl $0x3,0x8(%esp)
0x004013F5 movl $0x2,0x4(%esp)
0x004013FD movl $0x1,(%esp)
0x00401404 mov 0x1c(%esp),%eax
0x00401408 call *%eax
24 fp2(4, 5, 6);
0x0040140A movl $0x6,0x8(%esp)
0x00401412 movl $0x5,0x4(%esp)
0x0040141A movl $0x4,(%esp)
0x00401421 mov 0x18(%esp),%eax
0x00401425 call *%eax
25 fp3(7, 8, 9);
0x00401427 movl $0x9,0x8(%esp)
0x0040142F movl $0x8,0x4(%esp)
0x00401437 movl $0x7,(%esp)
0x0040143E mov 0x14(%esp),%eax
0x00401442 call *%eax
27 getch();
0x00401444 call 0x401c40 <getch>
29 return 0;
0x00401449 mov $0x0,%eax
30 }
0x0040144E leave
0x0040144F ret
--------------------------------------------------------------------------------
6 void function1(int a, int b) {
0x00401334 push %ebp
0x00401335 mov %esp,%ebp
0x00401337 sub $0x18,%esp
7 printf("\n Function1: a: %d, b: %d (%d)", a, b, __LINE__);
0x0040133A movl $0x7,0xc(%esp)
0x00401342 mov 0xc(%ebp),%eax
0x00401345 mov %eax,0x8(%esp)
0x00401349 mov 0x8(%ebp),%eax
0x0040134C mov %eax,0x4(%esp)
0x00401350 movl $0x403024,(%esp)
0x00401357 call 0x401c78 <printf>
8 }
0x0040135C leave
0x0040135D ret
所以,有人请指导/帮助我找到答案。
感谢。
答案 0 :(得分:4)
未定义行为的问题在于它可以做你想要的事情,在所有情况下做同样的事情都不是保证。 (即使在特定的结构上使用单个编译器)。对于定义的行为,有标准和实现特定的保证。你有可能永远找不到你觉得意外的东西,因为实施者决定做一些合理但不保证的事情。
在这种情况下,在堆栈上放置太多东西可能是无害的,因为被调用者只引用了X的前N个。
调用function3
后,4200926
获得d
。这不是定义的行为,也不会定义任何值,段错误或其他任何行为。这只是未定义的行为。
调用者并不总是清理堆栈,调用者可以在调用之前将堆栈恢复到其值,这不是定义的行为。这两种方法都是可以接受的,清理堆栈的功能纯粹是实现定义的,实现者甚至可以允许标志来改变它。
答案 1 :(得分:3)
我没有给出任何冗长的解释,我的回答是;它只是调用未定义的行为。
如果表示被调用函数的表达式具有不包含a的类型 原型,对每个参数执行整数提升,并对其进行参数化 将float类型提升为double。这些被称为默认参数 促销。如果参数的数量不等于参数的数量,那么 行为未定义。 1 ...
在这种情况下,你可能得到任何东西;预期或意外的输出。有时您可能会出现分段错误或有时程序可能会崩溃。
<子> 1。重点是我的。