今天讲解,内存空间在程序实际运行过程中的表现形式。通过前面几节的讲述,我们大概知道了,程序运行空间的内存分布,此处简单复习一下:
其实大多数情况下,开发程序的我们并不关注变量声明在堆上,还是在栈上,代码段和数据段之间到底挨着不挨着,与程序员何干?
但是某些情况下,我们确实需要理会这些内容的,最具有代表性的就是嵌入式开发,何为嵌入式开发,就是代码写完烧死了芯片中,这辈子不会再改变了。那日常生活中,哪些东西会涉及到嵌入式开发呢,举一个大家平时都会接触到的例子,手机。
手机上嵌满了各种芯片,这些芯片中,绝大多数奔跑着C语言代码,这些芯片每年是数以亿计的生产消耗,比如你要导航就需要GPS芯片;要听音乐就需要DSP芯片;到打电话,就需要GSM芯片;要刷微信,就需要LCD芯片,等等等等,为什么那么多高级语言,独独C语言选为大学理工科必修科目,是有他的道理的。
话说回来,为什么嵌入式开发就需要使用C语言呢,有这么几个原因,第一:C语言速度快,因为他是执行语言,不是解释语言,直接加载二进制程序,代码就会运行,不需要虚拟机;第二:C语言稳定,C语言的封装库很少,代码基本都是程序员码出来的,就就使得他可控性高,通过严格的测试,能够保证这些代码的正确性,不像android系统,总是会感觉内存不足,为什么苹果系统那么快,那么稳,因为IOS的本质是C;第三:空间占用少,嵌入式设备因为芯片体积小,决定了他不能拥有巨大的内存,所以C语言对于堆,栈的可见性就变得很重要。
C语言不同于其他高级语言,其他高级语言,则是不停的寻找,寻找别人封装好的库,用别人的,可以看作廉价拼凑;C语言每个功能的实现,都依赖程序员一行一行的敲代码,可以看作高端定制。对于其他高级语言来说,最重要的则是知道,我要知道,我每个接口调用以后,出来的结果是什么样的,知其然,不知其所以然。对于C程序来说,最重要的是掌控,我要知道我的程序编译出来,数据段是什么样的,代码段是什么样的,要知道代码跑起来,堆是什么样的,栈是什么样。
关注C语言的历史排名,可以看出从历史上讲C与java(高级语言代表)就深深的纠缠在一起。Java的兴起,主要依赖互联网和大数据;C的坚挺则是他在物联网芯片领域不可替代的作用。
言归正传,让我们来创建一个包含堆,栈,数据段,代码段的程序:
因为,嵌入式开发系统,基本分为两类,实时操作系统(ucos等),非实时操作系统(Linux等)。而Linux除了嵌入式开发,也广泛的应用于后台服务器的开发,Linux系统本身就是C语言编写的。所以我们选取的调试环境就是Linux,通过gdb工具,来观察程序的运行过程。
编译工具:gcc
gdb选项:-g
调试工具:gdb
执行文件:a.out
使用到的gdb命令:
set args 参数列表(为main传入参数)
b 函数名(在函数入口打断点)
r 运行程序
p 变量(打印变量值)
I proc mapping(查看进程内存映射)
n 下个语句,不进人函数
s 下个语句,进入函数
$gcc -g mem_gdb.c
分析运行中gdb log,结合程序进程空间,得出:
| 代码段(.text)
| 数据段(.data)/数据段(.bss)
| 堆(heap)
| 栈(stack)
| 开始
| 0x400000
| 0x600000
| 0x602000
| 0x7ffffffde000
| 结束
| 0x401000
| 0x602000
| 0x623000
| 0x7ffffffff000
|
符号名
| 类型
| 位置
| 所属
| main
| 函数
| 0x40056d
| 代码段(.text)
| func
| 函数
| 0x40055d
| 代码段(.text)
| argc
| 参数
| 0x7fffffffe4fc
| 栈(stack)
| argv
| 参数
| 0x7fffffffe4f0
| 栈(stack)
| a
| 全局变量
| 0x601034
| 数据段(.data)
| b
| 局部变量
| 0x7fffffffe50c
| 栈(stack)
| p
| 局部指针变量
| 0x602010
| 堆(heap)
|
补充说明:
(1)tui选项:gdb分析时候,也可以追加--tui选项打开界面显示:
tui可调节不同的视图,方法在(gdb)后输入命令,具体如下:
layout src
| Standard layout—source on top, command window on the bottom
| layout asm
| Just like the "src" layout, except it's an assembly window on top
| layout split
| Three windows: source on top, assembly in the middle, and command at the bottom
| layout reg
| Opens the register window on top of either source or assembly, whichever was opened last
| tuireggeneral
| Show the general registers
| tui reg float
| Show the floating point registers
| tuiregsystem
| Show the "system" registers
| tui reg next
| Show the next page of registers—this is important because there might be pages of registers that aren't in the "general", "float", or "system" sets
| (2)寄存器显示命令:
p $pc 程序计数器,当前运行到的行(值应该在text,否则异常);
p $sp 栈指针,当前栈顶(值应该在stack段,否则异常)
i r 查看所有寄存器,pc是r14。 |