This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.
今天写学校作业,题目中有涉及到函数指针的内容,闲来无事就多写了几个例子,结果发现了C++ 函数指针中一个比较有趣的现象。
我们先来分析一下以下代码能否正常运行:
1 | // 一个正常工作地 sum 函数, 两个参数都有 const 修饰符 |
首先,我们很明显的看到在typedef中对目标函数的签名和以上两个函数 sum 与 wrong_sub 都不符合,按理说构造这样的一个函数指针数组是会报错的。
可是我实验了一下,程序可以编译通过,上面的结果也都和对应的函数运行结果相同。
这就说明在这里:
- 要不然就是函数的参数中const限定符失效了
- 要不然就是typedef中关于目标函数参数的const修饰符没有生效
为了检查到底是哪里出现了问题,我开始对这段代码进行调试,首先下一个断点在函数结束处,然后观察 operators 数组中的变量:
在这里我们发现了两个奇怪的现象:
- 数组 operators 里面的两个函数实际的 Value 值与 Type 中的签名值并不相同 (Type 中带有 const 限制符,而实际值中 const 消失了)
- 当我们将两个函数的入口地址加入监视之后发现,两个指针指向的地址并不是函数的实际入口地址
从右边内存中可以看到,从地址 0x0034fd1c 开始有两个连续的指针在栈上,分别指向 0x008b119a 和 0x008b105a ,也就是左边监视列表中所对应的值。
那么这两个地址到底对应了什么呢?
我们进入反汇编查看:
很明显的,0x008b119a 这个地址的 sum 只有一条 jmp 的跳转,那么他跳转去了哪里呢?
0x008b2730 - 也就是我们真正 sum 函数的入口地址。
这是对应 0x008b2730 地址的 sum 函数,代码分配在代码常量区。
那么当我直接调用函数会发生什么呢?为了验证,我重新开始了程序,此处的sum函数地址如下:
这里 sum 函数地址在 0x013c3260
而在断点处所看到的汇编代码如下:
1 | 013C6E34 F2 0F 11 04 24 movsd mmword ptr [esp],xmm0 |
这里程序调用了 0x013c11ea 处的代码,那么这里又是什么呢?
由于不知道怎么用VS查看指定处的汇编代码,我用 OllyDbg 检查了以上地址的代码:
可以很明显的看到,在 0x013c11ea 处也仅仅是一条跳转指令,而目标地址就是我们的函数入口: 0x013c3260
基于以上的实验分析我们得出了两个结论:
- 无论是直接调用或者通过函数指针调用,编译器都会生成跳转向函数入口的一条 jmp 指令,并在执行这条指令之前进行一些其他准备操作
- 也正是因为如此,在 typedef 中对函数参数的 const 修饰符并不起作用,因为在编译期间编译器并不能检查到函数的内部指令,所以它会将这里的函数指针对应的所有参数 const 全部退化掉,所有在 typedef 中函数指针参数上的 const 就是个形式而已
在网上搜索到的很多帖子都说直接调用函数会直接 call 函数的入口地址,现在看来并不都是如此。
希望对大家有所帮助,我个人对汇编和C++也只是入门,有什么不对的地方还请大家指出,谢谢!
Comments