函数指针签名不匹配,在执行程序时仍未遇到任何问题

时间:2013-12-01 15:16:47

标签: c pointers function-pointers

我正在尝试分析提供给函数指针的额外或更少参数的变化,这些参数的大小不相同(参数更少或更多)。

考虑以下示例

#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

所以,有人请指导/帮助我找到答案。

感谢。

2 个答案:

答案 0 :(得分:4)

未定义行为的问题在于它可以做你想要的事情,在所有情况下做同样的事情都不是保证。 (即使在特定的结构上使用单个编译器)。对于定义的行为,有标准和实现特定的保证。你有可能永远找不到你觉得意外的东西,因为实施者决定做一些合理但不保证的事情。

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

调用function3后,4200926获得d。这不是定义的行为,也不会定义任何值,段错误或其他任何行为。这只是未定义的行为。

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

答案 1 :(得分:3)

我没有给出任何冗长的解释,我的回答是;它只是调用未定义的行为

n1570:6.5.2.2函数调用(P2):

  

如果表示被调用函数的表达式具有不包含a的类型   原型,对每个参数执行整数提升,并对其进行参数化   将float类型提升为double。这些被称为默认参数   促销。如果参数的数量不等于参数的数量,那么   行为未定义 1 ...

在这种情况下,你可能得到任何东西;预期或意外的输出。有时您可能会出现分段错误或有时程序可能会崩溃。


<子> 1。重点是我的。