C语言诠释--为什么内存是线性分布的。

来源:互联网 发布:日本网络用语 笑 编辑:程序博客网 时间:2024/06/05 18:51

Author:伟易达集团软件工程师 II 杨源鑫
Date :2016.11.11
Subject:内存为什么是线性分布的

      今天有位小伙伴问了我一个问题,问题大概是这样描述的:
      师兄,我如何能够先访问一个函数,接着我访问另外一个函数,然后再访问原来的那个函数,但是不能调用原来那个函数,我该怎么实现呢?看完这问题,还真有点饶口啊,其实他想说明的关键就是:函数指针。函数指针实现是太方便了,定义一个指针指向函数,这个指针就可以获取那个函数的入口地址。
     鉴于这个概念,我写了一个例子,来验证内存为什么是线性分布的。

     请看源码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#define abs(a,b) a < b ?(b-a):(a-b) //定义一个绝对值宏int add(int a , int b){eturn a+=b;}int sub(int a , int b){return a-=b;}int time(int a , int b){return a*=b ;}int main(int argc, char *argv[]){int a = 20 , b = 10 ;int (*addfun)(int , int) = add;int (*timefun)(int , int) = time ;int (*subfun)(int , int) = sub ;int offset = subfun-addfun ;int (*fpofset)(int , int) = subfun-offset;printf("%p--->%d\n",fpofset,fpofset(a,b));int offs = abs(addfun,timefun);int (*ftimefset)(int , int) = fpofset+offs;printf("%p---> %d\n",ftimefset,ftimefset(a,b));int *p = (int *)add ;int (*q)(int,int) = p ;printf("%d\n",q(a,b));return 0;}

首先我们看到,我定义了三个函数 add 和 sub 和 time,功能我就不多说了,大家都懂。

int (*addfun)(int , int) = add;int (*timefun)(int , int) = time ;int (*subfun)(int , int) = sub ;
以上三句学过 C 语言的都知道,这叫函数指针。Add,time,sub 是函数名,函数名其实就是函数的入口地址,赋值给函数指针也就说明一个道理,addfun,timefun,subfun 可以像我们平常使用函数一样,往里传参数,然后通过 printf 输出。

今天我们的重点不在这里,大家请看,以下是我加的 printf 语句,在上面没有,主要是为了演示我的结论。



我们可以观察到 int offset = subfun-addfun;这句话的效果就是将这两个函数的入口地址相减获取到他们之间的偏移量。然后通过函数指针 int (*fpofset)(int,int) = subfun-offset;用subfun 地址减去 subfun 和 addfun 之间的偏移量可以得到 addfun 的地址。这时候,往fpofset(a,b)传参我们可以得到结果是 30,那就说明,fpofset(a,b)此时是通过偏移地址获取到addfun 的地址,然后再通过 addfun 这个函数指针间接的访问到 add 这个我们定义的函数的入口地址,从而调用了 add 这个函数实现两数相加。看到这里可能各位看官有点晕呐,没事,再看看下面的分析,这次我要实现的是,将通
过函数指针的偏移实现间接访问获取 time 函数实现相乘。
      那如果用 addfun-timefun 的偏移地址给 offs,这样可以不呢?我们把上面的代码改一下:


     天啊,段错误了,程序异常退出! !为什么?想过原因吗?因为, addfun 的地址是 0x00401630,而 timefun 的地址是 0x0040164C,它们两的地址之差是 0xFFFFFFE4,这明显已经不是一个正常的地址了,是一个负数,我们都知道,负数的补码等于该数的绝对值的二进制形式按位取反再加一,所以后面将取得得这个偏移量赋值给了ftimeefset这个函数指针, 再用printf打印, 在这就出现错误了, 原来fpofset的地址是0x401630,你给这个地址加上一个 0Xffffffe4 能等于 timefun 的地址 0x0040164C 这个地址吗?这不扯蛋吗?所以,正确的做法应当是,给这相减的结果加一个绝对值,abs(a,b);先判断 a 和 b 这两个参数哪个比较大,然后再计算他们的偏移,这样就不会错误了。可以看看。


     仅仅是加了一个绝对值,我们可以看到打印结果,我们得到的偏移地址是 0x0000001C,这就是 timefun 和 addfun 之间相减的偏移量。 通过地址偏移量取到了 timefun 这个函数指针的地址, 因为 timefun 这个函数指针指向了 time 函数, 进而就调用了这个函数实现两数相乘了。我们还可以用以下的内存分布图来解释为什么是这样的:



0 0