深入研究C语言 第二篇
1. 程序一:
首先我们研究如下程序:
回答如下问题:
1. 程序运行时n,a,b,c的段地址在哪个寄存器中?
全局变量的存储空间在什么段里?局部变量的存储空间在什么段了?参数在什么段里?函数的返回值存储在什么地方?
全局变量的存储空间在什么时候分配?什么时候释放?
局部变量的存储空间在什么时候分配?什么时候释放?
2. 函数f3在调用与返回方式与函数f1与f2有何不同?
我们编译完成后,进入debug查看。
首先,我们执行到main函数处,然后开始单步执行。我们看到,每次单步执行时,涉及到取数据或者数据赋值的操作,debug都会显示出要操作的地址的地址和当前值。我们先来验证这个地址和值是否是正确的:
由于源程序中赋值n为0,不好验证是否正确,我们将n的赋值暂时改为n=10;我们验证如下:
(语句执行后DS:01a6处的值)
(语句未执行前DS:01a6处的值)
可以看到,n=10这条语句执行时,debug右侧显示出要操作的地址为DS:01A6,而程序执行后,DS:01a6处的值变为了0A,也就是说,被赋值的n的地址就是这里。由此,我们可以确定,debug单步执行时的地址和当前值是可信的。
我们依据每次单步执行时的debug值来确定每个变量的位置:
这里,n=0;全局变量的地址为:DS:01A6,DS段值为0B96。
执行到此,我们看到的依然是全局变量n的地址。
继续执行,我们进行到f2函数调用时。我们看到, f2(1,2);中的参数,2和1分别被压入了栈中。并且其参数压入的顺序是从右向左压入的。其段地址当然是SS。
在f2中参数被调用时,是用BP加偏移量的方式被调用。最后时,将结果给了AX。在这里变量C就没有再内存中赋值,而是直接用SI、AX来保存的过程值。而参数被调用的时候,用的段寄存器是SS。
另外在退出f2函数的过程中,我们看到:
在入栈是分别是AX(第二个参数)、AX(第一个参数)、BP、SI。而出栈是只将SI、BP出栈,并没有将AX出栈。在这个时候,参数就被释放掉了。
然后程序就返回了,所以我们在此处确定,函数返回值return是通过AX返回的。
将n赋值时,将变量AX的值赋值给了n。
这里也是n的赋值。
我们在编写这样一个程序:
从这里我们更清晰的看出局部变量是定义在栈段里。而且,我们看到在函数一被调用时就有sub sp+02,所以我们可以看出,在程序调用的时候就分配下了变量的空间。
所以:程序运行时,n的段地址是DS,A的段地址是SS,B的段地址是SS,C没有在数据段中,使用的是寄存器AX。
全局变量的存储空间在数据段中,局部变量的存储空间在栈段里,参数的存储空间在栈段里,函数的返回值储存在AX中。
全局变量的存储空间在执行到声明变量的语句时分配,程序结束后被释放。
局部变量在函数被调用时分配,在函数结束后被释放。
参数的存储空间在调用函数时分配,在函数调用结束后释放。
我们对比f1和f3调用的不同,发现:1.调用时,f1是call 偏移地址,而f3是call 段地址+偏移地址。2.返回时,f1是直接RET,f3是先RETF再RET。
2. 程序二:
我们编写如下程序:
问题:变量n与a的存储空间分配方式有何不同?
编译完成后进入debug跟踪。
我们看到,A在程序开始就被分配,A在偏移位置为0194的位置存放。而N则是PUSH进栈,这时候才在栈中分配的,在这之前是在SI中存放的。而且,C程序的编译器对程序作了优化,将语句简化。
我们做验证:
编写程序:
首先我们看到,C语言在实现的时候,并没有按照我们在C语言中的语句顺序这样一条一条的翻译,而在底层的实现方式是,将n=2与n++放在一起。且变量n放置在寄存器SI中。
3. 程序三:
我们编写程序如下:
问题:
1. 程序中所有变量的存储空间相邻么?tc2.0中,整型、字符型、长整数型数据的存储空间分别为多大?
2. 不同的数据类型对数据运算方式有何影响?
Debug查看main函数执行时a、b、c、a1、a2的地址。
观察知:a、b、c、a1、a2的地址分别为:0194、0196、0198、0199、019B。
我们看到他们之间的差值分别是:2、2、1、2。我们知道int型变量的大小是2字节,所以可以看到,这些变量是连续的。
而且我们还看到,在int和char行数据++的时候,都是用的inc指令,而long型的是用的ADD指令。说明不同类型的数据所对应的数据运算方法不同。
我们看到这里,long型的变量,先用add,在用adc指令。这是因为long型变量长度是4字节,而汇编没有四字节的加法。只能先将低两位进行加法,然后再将有高两位与溢出标志位进行加法(这里是因为低两位相加的时候有可能溢出,如果溢出,应该向高两位进1,,而adc指令是带溢出位进行运算的。这样以来就可以保证高两位的值是正确的)。这样来完整的完成一次四字节的加法。
我们查询TC的变量长度:
我们可以推测,a2后的变量的地址为019F。我们验证:
这里也说明不同类型的变量是连续的,也可以说明long占用了4个字节。
4. 程序四:
编写程序如下:
问题:变量a,b和他们的各个数据项的存储空间是如何分配的?
首先我们分析自定义数据类型:它是由一个int型,三个char型变量共同组成的。这样一个数据类型应该再内存中占用5个字节。而两个自定义数据类型的变量a、b,他们一个是全局变量,一个是局部变量。在分配时,应当一个在数据段中,一个在栈段中。
编译完成后debug查看结构体内的变变量来进行验证:
我们看到,在自定义的结构体stu类型的全局变量a中,其各个数据项的排列是连续的。且整个变量a都在数据段,所占用的长度为5个字节。
在在自定义的结构体stu类型的局部变量b中,其各个数据项的排列依然是连续的。且整个变量都在栈段里,所占用的字节为5个。并且,在这里我们发现,虽然是对栈段做操作,但是这里没有用PUSH指令,用的也是MOV指令。并且我们看到,在程序一开始的时候,
Sp就做了相应的修改,把b的空间分配出来了。
5. 程序五:
我们编写如下函数,看结构体变量是如何传递和返回的:
反汇编如下:
我们看到,在汇编中有LEA这条指令,LEA就是目标地址传送指令: 将一个近地址指针写入到指定的寄存器。格式:LEA reg16,mem16。
在程序中,我们看到这这样的调用,结合上下文我们可以确定是调用的func函数。我们看其反汇编的代码:
我们看到,程序将0b28给了ax,但是我们知道,ax存放子函数返回值的寄存器。那么ax中放的是自定义的数据类型的变量么?显而易见,ax是放不下5个字节的。那么,又没有可能是偏移地址呢?我们查看。
果然,在数据段中我们找到了自定义类型a的存放地址。也就是说,func函数返回自定义类型的变量是靠返回其在数据段中的偏移地址返回的。
我们查看f函数的语句,看到f函数的偏移地址为0256。
我们找到调用语句:
我们看到,F中是用栈传入的结构体变量。
所以,我们可以知道,自定义数据类型的变量在函数中返回时,是靠返回其在数据段中的偏移地址返回的。而其当做参数被调用时是借助栈机制传入函数的。
深入研究C语言 第二篇的更多相关文章
- 深入研究C语言 第二篇(续)
1. 关于如下的程序,关于结构体的拷贝,拷贝是拷贝到内存中的什么地方? 我们进入debug进行反汇编,单步等操作跟踪查看.发现: 在main中,我们看到call 0266应该对应的是转跳到func处执 ...
- 深入研究C语言 第一篇(续)
没有读过第一篇的读者,可以点击这里,阅读深入研究C语言的第一篇. 问题一:如何打印变量的地址? 我们用取地址符&,可以取到变量的偏移地址,用DS可以取到变量的段地址. 1.全局变量: 我们看到 ...
- 深入研究C语言 第一篇
一. 研究过程 1.第一章:创建编译环境: 我们首先下载TC2.0,找到其中与编译连接相关的程序和文件: (1) 编译器:TCC.exe (2) 连接器:tllike.exe (3) 相关文件:c0s ...
- 【OpenGL】第二篇 Hello OpenGL
---------------------------------------------------------------------------------------------------- ...
- 第二篇 SQL Server代理作业步骤和子系统
本篇文章是SQL Server代理系列的第二篇,详细内容请参考原文. SQL Server代理作业由一系列的一个或多个作业步骤组成.一个作业步骤分配给一个特定的作业子系统(确定作业步骤去完成的工作). ...
- 【译】第二篇 SQL Server代理作业步骤和子系统
本篇文章是SQL Server代理系列的第二篇,详细内容请参考原文. SQL Server代理作业由一系列的一个或多个作业步骤组成.一个作业步骤分配给一个特定的作业子系统(确定作业步骤去完成的工作). ...
- [翻译]Go与C#的比较,第二篇:垃圾回收
Go vs C#, part 2: Garbage Collection | by Alex Yakunin | ServiceTitan - Titan Tech | Medium 目录 译者注 什 ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- 深入理解javascript对象系列第二篇——属性操作
× 目录 [1]查询 [2]设置 [3]删除[4]继承 前面的话 对于对象来说,属性操作是绕不开的话题.类似于“增删改查”的基本操作,属性操作分为属性查询.属性设置.属性删除,还包括属性继承.本文是对 ...
随机推荐
- Tomcat并发数优化,修改service.xml性能调优 增加最大并发连接数
可以在控制台的启动信息里看见,默认状态下没有被打开nio配置,启动时的信息,如下: 2010-2-1 12:59:40 org.apache.coyote.http11.Http11Protocol ...
- Node入门(转)
原文链接:http://www.nodebeginner.org/index-zh-cn.html Node入门 作者: Manuel Kiessling翻译: goddyzhao & Gra ...
- 把内容生成txt文件
StringBuilder MailLog = new StringBuilder(); string logPath = txtFile + str + DateTime.No ...
- 传输层(2)-TCP连接的建立和终止、TIME_WAIT状态
1.TCP连接的建立和终止 1)三路握手 客户端发送一个SYN(同步)分解,告诉服务器客户将在连接中发送的数据的初始序列号. 服务器发送确认客户的SYN(ACK),同时自己也得发送一个SYN分节,它含 ...
- subversion(SVN)安装配置
简介subversion(简称svn)是近年来崛起的版本管理软件系统,是cvs的接班人.目前,绝大多数开源软件都使用svn作为代码版本管理软件.Subversion是一个版本控制系统,相对于的RCS. ...
- PRML
PRML 学习之 第一章 介绍 Introduction #欢迎共同学习和讨论,由于本文将不断修改,谢绝转载 模式识别问题具有重要且久远的历史.比如,16世纪开普勒发现行星运动定律,又如20世纪出发现 ...
- jshint创建配置文件
在项目中创建文件,并必须以 .jshintrc 命名: 例如 { "eqeqeq":true, "curly":true}
- MVC NPOI Linq导出Excel通用类
之前写了一个模型导出Excel通用类,但是在实际应用中,可能不是直接导出模型,而是通过Linq查询后获取到最终结果再导出 通用类: public enum DataTypeEnum { Int = , ...
- Python—变量
1.在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量 2.访问限制: class内部属性可以被外部 ...
- 三、HTTP抓包测试
package testHTTP; import java.io.BufferedReader;import java.io.IOException;import java.io.InputStrea ...