C++ 函数调用跳转 typedef 中 const 限定符的退化

tech

This article was last updated on <span id="expire-date"></span> days ago, the information described in the article may be outdated.

今天写学校作业,题目中有涉及到函数指针的内容,闲来无事就多写了几个例子,结果发现了C++ 函数指针中一个比较有趣的现象。

我们先来分析一下以下代码能否正常运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 一个正常工作地 sum 函数, 两个参数都有 const 修饰符
double sum(const double num1, const double num2){
return num1 + num2;
}

// 此处的 sub 函数参数没有加
double wrong_sub(double num1, double num2){
num1 -= 1; // 对第一个参数 -1
return num1 - num2;
}

int main(){

// 这里为了测试只给第一个参数加了 const 修饰符
typedef double (*func)(const double, double);

// 函数指针数组中有 sum, sub 两个函数指针
func operators[2] = {sum, wrong_sub};

std::cout < < operators[0](1., 2.) << std::endl;
std::cout << operators[1](1., 2.) << std::endl;

return 0;
}

首先,我们很明显的看到在typedef中对目标函数的签名和以上两个函数 sum 与 wrong_sub 都不符合,按理说构造这样的一个函数指针数组是会报错的。

可是我实验了一下,程序可以编译通过,上面的结果也都和对应的函数运行结果相同。

这就说明在这里:

  1. 要不然就是函数的参数中const限定符失效了
  2. 要不然就是typedef中关于目标函数参数的const修饰符没有生效

为了检查到底是哪里出现了问题,我开始对这段代码进行调试,首先下一个断点在函数结束处,然后观察 operators 数组中的变量:

在这里我们发现了两个奇怪的现象:

  1. 数组 operators 里面的两个函数实际的 Value 值与 Type 中的签名值并不相同 (Type 中带有 const 限制符,而实际值中 const 消失了)
  2. 当我们将两个函数的入口地址加入监视之后发现,两个指针指向的地址并不是函数的实际入口地址

从右边内存中可以看到,从地址 0x0034fd1c 开始有两个连续的指针在栈上,分别指向 0x008b119a 和 0x008b105a ,也就是左边监视列表中所对应的值。

那么这两个地址到底对应了什么呢?

我们进入反汇编查看:

很明显的,0x008b119a 这个地址的 sum 只有一条 jmp 的跳转,那么他跳转去了哪里呢?

0x008b2730 - 也就是我们真正 sum 函数的入口地址。

这是对应 0x008b2730 地址的 sum 函数,代码分配在代码常量区。

那么当我直接调用函数会发生什么呢?为了验证,我重新开始了程序,此处的sum函数地址如下:

这里 sum 函数地址在 0x013c3260

而在断点处所看到的汇编代码如下:

1
2
3
4
013C6E34 F2 0F 11 04 24       movsd       mmword ptr [esp],xmm0  
013C6E39 E8 AC A3 FF FF call sum (013C11EAh)
013C6E3E DD D8 fstp st(0)
013C6E40 83 C4 10 add esp,10h

这里程序调用了 0x013c11ea 处的代码,那么这里又是什么呢?

由于不知道怎么用VS查看指定处的汇编代码,我用 OllyDbg 检查了以上地址的代码:

可以很明显的看到,在 0x013c11ea 处也仅仅是一条跳转指令,而目标地址就是我们的函数入口: 0x013c3260

基于以上的实验分析我们得出了两个结论:

  1. 无论是直接调用或者通过函数指针调用,编译器都会生成跳转向函数入口的一条 jmp 指令,并在执行这条指令之前进行一些其他准备操作
  2. 也正是因为如此,在 typedef 中对函数参数的 const 修饰符并不起作用,因为在编译期间编译器并不能检查到函数的内部指令,所以它会将这里的函数指针对应的所有参数 const 全部退化掉,所有在 typedef 中函数指针参数上的 const 就是个形式而已

在网上搜索到的很多帖子都说直接调用函数会直接 call 函数的入口地址,现在看来并不都是如此。

希望对大家有所帮助,我个人对汇编和C++也只是入门,有什么不对的地方还请大家指出,谢谢!

Author: 桂小方

Permalink: https://init.blog/1574/

文章许可协议:

如果你觉得文章对你有帮助,可以 支持我

Comments