作者:mobiledu2502859073 | 来源:互联网 | 2023-05-16 22:42
(一)研究概述数据不仅可以存储在寄存器中,还可以存储在内存中。这次我们就研究在C语言中,怎样直接在内存中存储数据。以及这样做的一些延伸问题。另外,在附录研究中,我们还探究了C语言中循环和分
(一) 研究概述
数据不仅可以存储在寄存器中,还可以存储在内存中。这次我们就研究在C语言中,怎样直接在内存中存储数据。以及这样做的一些延伸问题。另外,在附录研究中,我们还探究了C语言中循环和分支结构的实现。
(二) 研究过程
1) 直接在C语言中使用内存空间
此处援引书中的话:
对于存储空间来说,要使用他们一般都需要给出两个信息:一是指明存储空间所在、是哪个的信息;二是指明存储空间有多大的类型信息。
对于寄存器来说,就需要给出寄存器的名称,寄存器的名称就也包含了他们的类型信息。
对于内存空间来说,就需要给出地址(准确的说,是内存空间首地址)和空间存储数据的类型。
我们知道,在C语言里,用指针型数据来表示内存空间的地址和空间存储数据的类型。比如要向偏移地址为2000h、存储一个字节的内存空间写入一个字符‘a’,我们用如下的方法:
*(char *)0x2000 = ’a’;
第一个‘*’表示要访问的是一个内存空间;
“0x2000”是一个数值(0x表示十六进制),“(char *)”里面的‘*’指明了这个
数值表示一个内存空间地址,“char”指明了这个地址是存储char型数据的内存空间地址。
当然也可以用给出段地址和偏移地址的方法访问内存空间,比如我们要向地址为2000:0、存储一个字节的内存空间写入字符‘a’,如下:
*(char far *)0x20000000=’a’;
“far”指明内存空间的地址是段地址和偏移地址,“0x20000000”中的“0x2000”给出了段地址,“0000”给出了偏移地址。
由此,我们知道了C语言直接访问内存空间的基本方法。
2) 编写一个直接访问内存的C语言程序
我们编写一个程序um1.c如下:
![clip_image001 clip_image001](https://www.#.com/imgs/5/6/7/5/19/dc988b00def8f62eaf91ef1cbd42e7f1.jpe)
编译链接完成,debug加载反编译如下:
![clip_image003 clip_image003](https://www.#.com/imgs/7/5/5/3/90/8f18b36cdd4adcde855b1d889f93fa21.jpe)
![clip_image005 clip_image005](https://www.#.com/imgs/4/0/1/6/38/2999c32e7bc56f48d3e757f45f28a965.jpe)
3) 编写一个程序,用一条C语言实现在屏幕中间显示一个绿色的字符“a”
我们编写源程序如下:
![clip_image006 clip_image006](https://www.#.com/imgs/0/7/7/0/13/a635ca9797f7c5638956636589d6d0e7.jpe)
编译链接运行,效果如下:
![clip_image008 clip_image008](https://www.#.com/imgs/7/5/0/0/58/e241907aefa2f45096fb72186dfa84d8.jpe)
4) 关于全局变量和局部变量
我们分析下面程序中所有函数的汇编代码:
![clip_image009 clip_image009](https://www.#.com/imgs/0/8/5/1/74/83ee261a59ebbde1adb8e3aea068e034.jpe)
编译链接之后debug加载,反汇编如下。
![clip_image011 clip_image011](https://www.#.com/imgs/7/1/9/4/74/b7dfd95a8e4147129ad063d3e1e166e6.jpe)
![clip_image013 clip_image013](https://www.#.com/imgs/2/0/3/0/71/6294d4eb68e2541bfb06d982540ff430.jpe)
![clip_image015 clip_image015](https://www.#.com/imgs/1/9/2/3/82/b159a9b329b74060f737d90ad29d1ea1.jpe)
在这里,我们非常直观的看出。程序中的全局变量,被放在了程序的数据段中,而局部变量则放在了其栈段中。这也正好说明了在每个函数开头,都有push bp;mov bp sp的作用。我们把局部变量放在了栈段中,那我们使用时必然要顺利的找到这个变量。而使用sp的话,假如程序中有入栈出栈的操作,栈顶指针变了,我们就找不我们存放的变量了。所以使用BP这个寄存器,在程序的开始把Sp的值给BP,然后,将SP的值增加,把局部变量的位置留出来。这时,我们可以方便的用BP找到局部变量,而也可以方便的使用栈的功能。函数返回时,BP的值又赋值给SP,函数内的局部变量就消失了。这也就是为什么C语言中局部变量的值只在函数内有效的原因。
5) C语言函数返回值存放在哪儿?
我们研究了变量的存放位置,那么,C语言的函数返回值存放在什么地方呢?我们写如下的程序。
![clip_image016 clip_image016](https://www.#.com/imgs/3/8/0/7/83/58623e5ec42c8bf936c19a2cd5a163e6.jpe)
编译链接后反汇编分析:
![clip_image018 clip_image018](https://www.#.com/imgs/2/8/5/9/93/07dccc8dd6b6f9049a88d67ef26afe2c.jpe)
![clip_image020 clip_image020](https://www.#.com/imgs/7/8/9/5/64/0316b6ac89e32849fe5dd20eeeed7ab9.jpe)
我们看到020A处,为函数f()的汇编指令。前五句,根据前面的内容,我们很容易理解。但是不同的是,多了一句mov ax,[01AA]。那么这条语句的作用是什么?会不会跟我们函数返回值有关?是不是函数返回值是用AX传出的呢?
我们调试运行程序:g 021d,结果如下
![clip_image022 clip_image022](https://www.#.com/imgs/0/6/1/9/40/a1fae4cdb297df9d9b55faa25b038bae.jpe)
可见,函数的返回值的确是由寄存器ax传出的。
6) 综合分析
我们编写一个程序如下:
![clip_image023 clip_image023](https://www.#.com/imgs/4/0/4/3/14/988a55426f3207c6038641570b1a1d2f.jpe)
首先我们要了解#define和malloc这两条语句。
#define的作用,是宏定义,简单的讲在这里就是将((char *)*(int far *)0x02000000)这条语句用Buffer代替,以后再用到这条语句的时候,直接用Buffer就可以了。
((char *)*(int far *)0x02000000)表面是一个char型的指针,它指向的是0200:0000这个地址。
malloc(X)的作用,是申请X长度的内存。
Buffer=(char *)malloc(20)是申请20个字节的内存单元,将首地址赋值给Buffer指向的内存单元。
理解了这个,我们可以理解这个C程序的内容
我们编译链接,反汇编如下:
![clip_image025 clip_image025](https://www.#.com/imgs/0/3/4/7/85/1b849b06f217af98fb80832d997ea0df.jpe)
![clip_image027 clip_image027](https://www.#.com/imgs/1/0/3/9/21/814b16a5891315c236c39568c6339876.jpe)
![clip_image029 clip_image029](https://www.#.com/imgs/1/5/3/7/81/cb656ac13720208852a71701f37e8d5f.jpe)
我们单步执行中,发现
![clip_image031 clip_image031](https://www.#.com/imgs/1/0/0/6/45/8b51b8062e9a70db7fa39b025977cd73.jpe)
AX中存放的应该是由malloc申请返回来的。应该是我们申请内存的首地址。
我们继续执行
![clip_image033 clip_image033](https://www.#.com/imgs/4/8/3/8/2/7676cd8eb195fec1e603572edd67fddc.jpe)
我们发现这个地址果然是malloc返回来的申请内存的偏移地址。
(三) 附录研究
注:附录研究与内存单元的使用无关,主要是研究C语言中的三种结构。顺序结构我们可以很容易的理解,循环结构和选择分支结构研究如下:
1. C语言中循环结构for语句的实现
C语言中循环语句,for语句是如何实现的呢?我们编写如下程序
![clip_image034 clip_image034](https://www.#.com/imgs/9/7/2/9/82/249792a36eb2c9d7feef72693cf3cb3a.jpe)
我们尽量的减小问题的复杂程度,使其突出我们所要研究的重点。这里循环内并没有其他的语句,仅仅是一条空语句。就是为了便于我们把问题的焦点放在for循环的实现上。
我们编译连接,加载后反汇编查看,如下:
![clip_image035 clip_image035](https://www.#.com/imgs/8/7/4/8/30/27ee6dafdc9262ef2b8cb51c74cd8d48.jpe)
我们可以看到,for的循环是使用了si寄存器,首先将si入栈保存,然后清空si,通过不断自加si的值,然后将si与我们原定的数值进行比较。
那么,如果在for中加入语句的话,那么这条语句会加在哪里呢?
我们分析,jmp 0203这条语句,这应该是转到循环内的。那么,应该是先比较,后执行循环内语句呢?还是先执行循环内语句,后比较呢?
我们接着分析,si的值是从0开始的,最后与5作比较,小于则转跳。如果先比较后执行的话,执行的次数就成了4次,这是不正确的。所以,应该是先执行,后比较。
我们验证:
![clip_image037 clip_image037](https://www.#.com/imgs/4/2/6/3/73/b3fca5dffd82aa27b3f00d19e23bcbb0.jpe)
我们在看jmp,这次直接jmp到了020B,果真是每次循环先执行,后比较。但是同时,我们发现,一进入for循环的时候,他是转跳到了比较语句。这又是为什么呢?
假如我们写这样的C语句:for(i=0;i<1;i++)会出现什么结果?如果不先比较一下的话,循环内语句必然要执行一次。这就是一进入for循环,必须先比较一下的原因。
2. C语言中分支结构if的实现
我们编写一个程序如下:
![clip_image038 clip_image038](https://www.#.com/imgs/4/8/7/9/89/7ae19be843a14aa1f455af65237f4795.jpe)
编译连接后加载查看如下:
![clip_image040 clip_image040](https://www.#.com/imgs/3/8/3/9/36/14a9cd60f747b3513cdca88563e50c8d.jpe)
我们可以直观的看到,依然是使用cmp,jnz等比较语句来进行程序的选择分支的实现。
所以,参照这个研究,返回头看我们的汇编程序。我们可以更加系统的使用汇编语言,来实现程序的各种选择,循环结构。
(四) 个人感悟
内存单元,寄存器,变量之间的关系到底如何?这次的研究给出了一部分答案。局部变量的内涵,全局变量的内涵,返回值的实现。拿研究C语言后的汇编知识在反观汇编程序的实现,应该会有更深入的理解,更结构化的编程概念。