CPU阿甜:函数挪用的奥秘

2周前 (11-21 07:57)阅读1回复0
kanwenda
kanwenda
  • 管理员
  • 注册排名1
  • 经验值84435
  • 级别管理员
  • 主题16887
  • 回复0
楼主

我是CPU阿甜, 前次我给各人许诺过,要讲一讲函数挪用的奥秘, 那个确实有点复杂, 想深入的理解机器代码层面的函数挪用不随便。

我也是从无数的指令中悟出那个函数挪用的奥秘的, 所以渐渐来,不要急。 放松心绪, 渐渐的品尝, 你可能需要多看几遍才气大白。

但是你一旦理解了,绝对物超所值,因为你会领会到汇编,存放器,指针,以及他们在一路到底是怎么工做的。

起首, 一个法式一条一条的指令都的老诚恳实的放在内存的一个处所,那个处所是Linux老迈分配的, 我干预不了, 但是那些指令都是我打德律风给硬盘, 让他给运输到内存的。

然后Linux老迈就会告诉我法式的进口点, 其实就是第一条指令的存放地址, 我就打德律风问内存要那个指令, 取到指令以后就起头施行。

那些指令傍边无非有那么几类:

1. 把数据从内存加载我的存放器里

什么? 你不晓得啥是存放器? 存放器就是我内部的一个暂时的数据存储空间了

2. 对存放器的数据停止运算, 例如把两个存放器的数加起来

3. 把我存放器的数据再写到内存里

但是我一旦碰着像如许的指令。

"把存放器ebp的值压到栈里往“

我就晓得好戏要上场了, 函数挪用就会起头。

我们那些x86系统的机器有个特征,就是每个函数挪用城市创建一个所谓的“帧”

哈哈, 不要被那些术语吓坏, 其实帧也就是我哥们内存中的一段持续的空间罢了。

像如许:

多个函数帧在内存里排起来, 就像一个先辈后出的栈一样, 不外,那个栈不像我们常见的栈, 栈底鄙人面。

相反,那个栈的栈底在上面, 是从上往下生长的 (或者说是从高地址向低地址生长的)

内存经常向我抱怨: "阿甜,你晓得吗, 每次我看到那个栈, 都有一种实气逆行的觉得, 半天都调整不外来 "

但内存不晓得, 我有一个喊ebp的特殊存放器, 不断会指向当前函数在一个栈的起头地址。

我还有别的一个特殊存放器,喊做esp , 他会跟着指令的运行,指向函数帧的最初的地址, 像如许:

如今那个指令来了:

"把存放器ebp的值压到栈里往“

"把esp的值赋给ebp"

你看看, 是不是新的函数帧生成了?

只不外如今只要一行数据。 ebp和esp指向统一地址。

函数帧的第一行的地址是800, 里边的内容是1000, 也就是上个函数帧的地址

重视, 我们每次操做的是4个字节,所以本来esp 的地址是804, 如今酿成了800

我又问内存要下一条指令:

"把esp 的值减往24”

下面几条指令是如许的:

“把10放到ebp 减往4的地址” (其实就是796嘛)

“把20放到ebp减往8的地址” (其实就是792嘛)

你们晓得那是干什么吗?

我想了良久才大白那是干嘛, 那其实就是在分配函数的部分变量啊

我猜源代码应该是如许的:

int x = 10;

int y = 20;

在我看来, x, y 只是变量, 他们喊什么底子不重要, 重要的是他们的值和地址!

下面几条指令很有意思:

" 把地址796做为数据放到 esp指向的地址“ (其实就是776嘛)

" 把地址792做为数据放到 esp+4指向的地址" (其实就是780嘛)

那又是在干嘛?

那其实就相当于把 x 的指针 x和 y 的指针 y ,放到了特定的处所, 预备着要做什么工作 , 可能要挪用函数了。

所以,所谓的指针就是地址罢了。

我猜法式员写的代码应该是如许:

int x = 10;

int y = 20;

int sum= add(x, y);

接下来的指令是如许:

“挪用函数 add”

我看到如许的函数就需要特殊小心, 因为我必需要找到 add函数返回以后的那条指令的地址, 把它也压到栈里往。

int x = 10;

int y = 20;

int sum = add(x, y);

printf("the sum is %d\n",sum); 假设那条指令的地址是100

重视啊, 把函数挪用完毕的以后的返回地址100压进栈以后, esp 也发作改变了, 指向了772的位置

我会找到函数Add 的指令,陆续施行

"把存放器ebp的值压到栈里往“

"把esp的值赋给ebp"

"把存放器ebx的值压进栈”

你看每个函数的起头指令都是如许, 我猜那应该是一种约定吧

那里额外把ebx那个存放器压进栈, 是因为ebx可能被上个函数利用, 但是在add函数中也会用 , 为了不毁坏之前的值, 只要先委屈一下暂时放到内存里吧。

接下来的指令是:

“把ebp 加8的数据取出来放到 edx 存放器” (ebp+8 不就是地址776嘛, 此中存放的是x的地址, 那就是取参数了)

“把ebp 加12的数据取出来放到 ecx 存放器” (ebp+12 不就是地址780嘛, 此中存放的是y的地址)

重视啊, 如今edx的值是796, ecx的值是792 , 但他们仍然不是实正的数据, 而是指针(地址)!

“把edx 指向的内存地址(796)的数据取出来,放到ebx 存放器”

“把ecx 指向的内存地址(792)的数据取出来,放到eax存放器”

此时此刻, 末于取到了实正的值, ebx = 10, eax = 20

你晕了没有?

假设你到此已经晕了, 定见你再读一遍。

我想源代码应该十分的简单,就是如许:

int add(int *xp , int *yp){

int x = *xp;

int y = *yp;

“把ebx 和 eax 的值加起来,放到 eax存放器中”

那个指令我最擅长做了。

接下来的指令也很关键, add 函数已经挪用完成, 预备返回了

“把esp 指向的数据弹出的ebx存放器”

“把esp 指向的数据弹出到ebp存放器”

你看add 函数帧已经消逝了, 或者换句话说, add 函数帧的数据还在内存里, 只是我们不在关心了!

接下来的指令十分的关键:

"返回"

我就会取出阿谁返回地址, 也就是 100, 往那里找指令接着施行

其实就是那条语句: printf("the sum is %d\n",sum);

问你一个问题, sum的值在那里保留着呢?

对, 是在eax存放器里 !

搞定了,看着很复杂, 其实看透了也挺简单吧。 函数挪用,关键就是

(1)把参数和返回地址预备好,

(2)然后各人都遵照约定, 每次新函数都要成立新的函数帧:

"把存放器ebp的值压到栈里往“

"把esp的值赋给ebp"

(3) 函数挪用完了, 重置 ebp 和esp ,让他们从头指向挪用着的栈帧。

好了,今天就到此为行 , 把我也累坏了, 仆人又要关机了, 留一个问题吧:

C语言编译,链接以后间接就是机器码, 那函数挪用的操做都是上面讲的。

但是关于Python, Ruby 如许的阐明型语言, 或者关于java 如许的有虚拟机的语言, 他们的函数挪用是什么样的? 和上面讲的有什么关系?

0
回帖

CPU阿甜:函数挪用的奥秘 期待您的回复!

取消
载入表情清单……
载入颜色清单……
插入网络图片

取消确定

图片上传中
编辑器信息
提示信息