C语言诠释--为什么内存是线性分布的。
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 函数, 进而就调用了这个函数实现两数相乘了。我们还可以用以下的内存分布图来解释为什么是这样的:
C语言诠释--为什么内存是线性分布的。的更多相关文章
- C语言中的内存分配与释放
C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...
- C语言中的内存压缩技术
C语言中的内存压缩技术 前言 在整个研究生阶段我都在参与一个LTE协议栈实现的项目,在这个项目中,我们利用一个自己编写的有限状态机框架将协议栈中每一层实现为一个内核模块.我们知道,在编写内核代码时需要 ...
- C语言中的内存管理
开始陆续的发一下唐老师视频的笔记吧,顺便带一些正冲哥书的的内容.不能一下都发出来,因为内容发多了自己也受不了,而且发的都是学习视频时候的一些笔记,可能会有一些问题不是很清晰. 先说一下C语言中的内存管 ...
- C语言中的内存对齐
最近看了好多,也编了好多C语言的浩强哥书后的题,总觉的很不爽,真的真的好怀念linux驱动的代码,好怀念那下划线,那结构体,虽然自己还很菜. 同时看了一遍陈正冲老师的C语言深度剖析,收益很多,又把唐老 ...
- C语言数据在内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.程序结束时由编译器自动释放 ...
- C语言变量声明内存分配
转载: C语言变量声明内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结 ...
- 数据结构基础(1)--数组C语言实现--动态内存分配
数据结构基础(1)--数组C语言实现--动态内存分配 基本思想:数组是最常用的数据结构,在内存中连续存储,可以静态初始化(int a[2]={1,2}),可以动态初始化 malloc(). 难点就是数 ...
- 2-Linux C语言指针与内存-学习笔记
Linux C语言指针与内存 前面我们对于: c语言的基本用法 makeFile文件的使用 main函数的详解 标准输入输出流以及错误流管道 工具与原理 指针与内存都是c语言中的要点与难点 指针 数组 ...
- C语言程序的内存布局
C语言程序的内存布局 一:C语言程序的存储区域 C语言编写的程序经过编绎-链接后,将形成一个统一的文件,它由几个部分组成,在程序运行时又会产生几个其他部分,各个部分代表了不同的存储区域: 1.代码段( ...
随机推荐
- 深入解析 SQL Server 高可用镜像实现原理
作者:郭忆 本文由 网易云 发布. SQL Server 是 windows 平台 .NET 架构下标配数据库解决方案,与 Oracle.MySQL 共同构成了 DB-Engines Ranking ...
- [COGS 2287][HZOI 2015]疯狂的机器人
Description 题库链接 现在在二维平面内原点上有一只机器人,他每次可以选择向右走,向左走,向下走,向上走和不走(每次如果走只能走一格).机器人不能走到横坐标是负数或者纵坐标是负数的点上. 给 ...
- [HNOI 2016]网络
Description 一个简单的网络系统可以被描述成一棵无根树.每个节点为一个服务器.连接服务器与服务器的数据线则看做 一条树边.两个服务器进行数据的交互时,数据会经过连接这两个服务器的路径上的所有 ...
- [Codeforces 863A]Quasi-palindrome
Description Let quasi-palindromic number be such number that adding some leading zeros (possible non ...
- ●BZOJ 3640 JC的小苹果
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3640题解: 期望dp,高斯消元 设dp[i][h]在i位置且血量为h这个状态的期望经过次数. ...
- ●洛谷P3688 [ZJOI2017]树状数组
题链: https://www.luogu.org/problemnew/show/P3688题解: 二维线段树. 先不看询问时l=1的特殊情况. 对于一个询问(l,r),如果要让错误的程序得到正确答 ...
- SpringBoot学习之集成mybatis
一.spring boot集成Mybatis gradle配置: //gradle配置: compile("org.springframework.boot:spring-boot-star ...
- Java锁机制了解一下
前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 只有光头才能变强! 本文章主要讲的是Java多线程加锁机制,有两种: Synchro ...
- @RequestBody和@RequestParam区别
@RequestParam 用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容.(Http协议中,默认传递的参数就是applicati ...
- DecimalFormat(用于格式化十进制数字)
1概念: DecimalFormat 是 NumberFormat 的一个具体子类,用于格式化十进制数字.该类设计有各种功能,使其能够分析和格式化任意语言环境中的数,包括对西方语言.阿拉伯语和印度语数 ...