gpt4 book ai didi

c - 函数指针签名不匹配,执行程序时仍然没有遇到任何问题

转载 作者:太空宇宙 更新时间:2023-11-04 00:38:23 25 4
gpt4 key购买 nike

我正在尝试分析提供给函数指针的大小不兼容(更少或更多参数)的额外或更少参数发生了什么。

考虑下面的例子

#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 和 fp3 采用与函数指针不同的参数大小(arguments)。但是我在执行程序时仍然没有观察到任何未定义的行为。

程序的输出是

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 ,已经提到被调用者/调用者需要像这样清理堆栈

被调用者清理[编辑]当被调用者从堆栈中清除参数时,需要在编译时知道堆栈需要调整多少字节。因此,这些调用约定与可变参数列表不兼容,例如打印()。然而,它们可能更节省空间,因为不需要为每个调用生成展开堆栈所需的代码。使用这些约定的函数在 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

那么,有人可以指导/帮助我找到答案吗。

谢谢。

最佳答案

未定义行为的问题是它可以做你想做的事,只是不能保证在所有情况下都做同样的事情。 (即使在特定架构上给出了一个编译器)。对于定义的行为,有标准和特定于实现的保证。并且您可能永远不会发现您认为出乎意料的事情,因为实现者已决定做一些合理但不保证的事情。

在这种情况下,在堆栈上放太多东西可能是无害的,因为被调用者只引用压入的 X 的前 N ​​个。

当调用 function3 时,它为 d 获取 4200926。这不是定义的行为,也不是任何值、段错误或任何其他定义的行为。这只是未定义的行为。

清理堆栈的并不总是被调用方,调用方可以将堆栈恢复到调用前的值,这不是定义的行为。任何一种方法都是可以接受的,清理堆栈的功能纯粹是实现定义的,实现者甚至可以允许标志来改变它。

关于c - 函数指针签名不匹配,执行程序时仍然没有遇到任何问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20313668/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com