章节结构
-
始终确保全局变量用的内存空间
-
临时确保局部变量用的内存空间
-
循环处理的实现方法
-
条件分支的实现方法
-
了解程序运行方式的必要性
始终确保全局变量用的内存空间
C语言中,在函数外部定义的变量称为全局变量。在函数内部定义的变量称为局部变量。
// 定义被初始化的全局变量
int a1 = 1;
int a2 = 2;
// 定义没有初始化的全局变量
int b1,b2;
//定义函数
void MyFunc()
{
// 定义局部变量
int c1,c2,c3,c4;
// 给局部变量赋值
c1 =1;
c2 = 2;
c3 = 3;
c4 = 4;
// 把局部变量的值赋给全局变量
a1 =c1;
a2 = c2;
b1 = c3;
b2 = c4;
}
汇编语言
_DATA segment dword public use32 'DATA'
_a1 label dword (4)
dd 1 (5)
_a2 dd 1
_DATA ends
_BSS segment dword public use32 'BSS'
_b1 label dword
db 4 dup(?) (6)
_b2 label dword
db 4 dup(?) (6)
_BSS ends
_TEXT segment dword public use32 'CODE'
_MyFunc proc near
push ebp
move ebp,esp
_MyFunc endp
_TEXT ends
操作码 | 操作数 | 功能 |
---|---|---|
add | A,B | 把A的值和B的值相加,并把结果存入A |
call | A | 调用函数A |
cmp | A,B | 对A和B的值进行比较,比较结果会自动存入标志寄存器中 |
inc | A | A的值加1 |
jge | 标签名 | 和cmp命令组合使用。跳转到标签行 |
ji | 标签名 | 和cmp命令组合使用。跳转到标签行 |
jle | 标签名 | 和cmp命令组合使用。跳转到标签行 |
jmp | 标签名 | 将控制无条件跳转到指定标签行 |
mov | A,B | 把B的值赋给A |
pop | A | 从栈中读取出数值并存入A中 |
push | A | 把A的值存入栈中 |
ret | 无 | 将处理返回到调用源 |
xor | A,B | A和B的位进行一伙比较,并将结果存入A中 |
编译后的程序会被归类到名为段定义的组。在Borland C++使用 规范中:
初始化的全局变量,会被汇总到名为_DATA的段定义中,
没有初始化的全局变量,会被汇总到名为_BSS的段定义中
指令会被汇总到名为_TEXT的段定义中。
_DATA segment和_DATA ends、_BSS segment和_BSS ends、_TEXT segment和_TEXT ends,这些都是表示各 段定义范围的伪指令。
编译后的函数名和变量名前附加一个下划线
规定int类型的长度是4字节。
程序运行时没有初始化的全局变量的领域(_BSS段定义)都会被设定为0进行初始化。
标签表示的是相对于段定义起始位置的位置。
_DATA段定义的内容。(4)中的_a1 label dword 定义了_a1标签。
由于_a1在_DARA段定义的开头位置,所以相对位置是0。_a1相当于全局变量a1。
(5)中的dd 1指的是,申请分配了4字节的内存空间,存储着1这个初始值。
dd(define double word)表示的是由两个长度为2的字节领域(word),也就是4字节。这里也定义了相当于全局变量a1、a2的标签_a1、_a2。它们各自的初始值也都被存储在4字节的领域中。
_BSS段定义的内容 定义了相当于全局变量b1、b2的标签_b1、_b2. (6)的db 4 dup(?)表示的是申请分配了4字节的领域,但值尚未确定(这里用?来表示)。
db(define byte)表示有1个长度是1字节的内存空间。
因而db 4 dup(?)表示的就是4个长度是1字节的内存空间
db 4 表示的是双字节(= 4字节)的内存空间中存储的值是4.
从上面_DATA和_BSS的段定义中,全局变量的内存空间都得到了确保。因而,从程序的开始到结束,所有部分都可以参阅全局变量。
在Borland C++中,程序运行时没有初始化的全局变量的领域(_BSS段定义)都会被设定为0进行初始化。
所以根据是否进行了初始化把全局变量的段定义划分为了两部分。
通过汇总,初始化很容易实现,只要把内存的特定范围全部设定为0就可以了。
临时确保局部变量用的内存空间
局部变量只能在定义该变量的函数内进行参阅?
因为,局部变量是临时保存在寄存器和栈中的。
- 函数内部利用的栈,在函数处理完毕后会恢复到出示状态,因此局部变量的值也就被销毁了,
- 而寄存器也可能被用于其他目的。
寄存器空闲时使用寄存器,寄存器空间不足时就是用栈。
Borland C++编译器对于局部变量会尽可能的使用寄存器,这是最优化的运行结果。
(8)表示的是往寄存器分配局部变量的部分
仅仅对局部变量进行定义是不够的,只有在给局部变量赋值时,才会被分配到寄存器的内存区域。 当寄存器不多而局部变量很多时,寄存器只是被单纯地用于存储变量的值,与其本身的角色没有任何关系,
x86xilie CPU 拥有的寄存器中,程序可以操作的有十几个。其中空闲的,最多也只有几个。因而,局部变量数目很多的时候,可分配的寄存器就不够了。这种情况下,局部变量就会申请分配栈的内存空间。虽然栈的内存空间也是作为一种存储数据的段定义来处理的,但在程序各部分都可以共享并临时使用这一点上,它和_DATA、_BSS段定义在性质上还是有些差异的。 如:在函数入口处位变量申请分配栈的内存空间的话,就必须在函数出口处进行释放。否则,经过多次调用函数后,栈的内存空间就会被用光了。
mov eax,1
mov edx,2
mov ecx,3
mov ebx,4
循环处理的实现方法
通过利用for循环以及if条件分支来改变程序流程的机制称为流程控制。
循环计数器:用来计算循环次数的变量。
对比C语言和汇编语言的源代码
// 定义MySub()函数
void MySub()
{
// 不做任何处理
}
// 定义MyFunc函数
void MyFunc()
{
int i;
for (i = 0;i < 10;i++)
{
//重复调用MySub()函数10次
}
}
// for语句转换成汇编语言
xor ebx,ebx ;将eax寄存器请0
@4 call _MySub ;//调用MySub函数
inc ebx ;//ebx寄存器的值加1
cmp ebx,10 ;//将ebx寄存器的值和10进行比较
jl short @4 ;//如果小于10 就跳转到@4
MyFunc 函数中用到的局部变量只有i,变量i申请了ebx寄存器的内UC你空间。
for语句的括号中的i = 0;被转换成了 xor ebx,ebx这一处理。
XOR为异或运算,即同1为0,同0为0,其他为1(相同数值为0,不同数值为1)
因此不管当前ebx寄存器的值是什么,记过肯定为0。move ebx,0也可以得到相同的结果。但是xor指令处理的速度更快。(这里,体现了编译器的最优化功能)
ebx寄存器的值初始化后,会通过call指令调用MySub函数(_MySub)。从MySub函数返回后,则会通过inc 指令对ebx寄存器的值做加1处理。相当于for语句中i++
下一行的cmp指令是用来对第一个操作数和第二个操作数的数值比较的指令。
cmp ebx,10就相当于C语言的i<10这一处理,意思是把ebx寄存器的数值同10进行比较
汇编语言比较指令的结果,会存储在CPU的标志寄存器中。
但是,标志寄存器的值,程序是无法直接参考的
程序是怎么判断比较结果的?
- 汇编语言比较指令的结果,会存储在CPU的标志寄存器中。
- 但是,标志寄存器的值,程序是无法直接参考的
- 汇编语言中有多个跳转指令,这些跳转指令会根据标红字寄存器的值来判定是否需要跳转。
如最后一行的jl,是jump on less than(小于的话就跳转)的意思。
即jl short @4表示,前面运行的比较指令的结果若“小”的话就跳转到@4这个标签
条件分支的实现方法
条件分支的实现方法同循环处理的实现方法类似,使用的也是cmp指令和跳转指令。
- 进行条件分支的C语言源代码
// 定义MySub1函数
void MySub1()
{
// 不做任何处理
}
// 定义MySub2函数
void MySub2()
{
// 不做任何处理
}
// 定义MySub3函数
void MySub3()
{
// 不做任何处理
}
// 定义MyFunc函数
{
int a = 123;
// 根据条件调用不同的函数
if(a > 100)
{
MySub1();
}
else if(a < 50)
{
MySub2();
}
else
{
MySub3();
}
}
- MyFunc函数转换成汇编语言后的结果
_MyFunc proc near
push ebp;
mov ebp,esp;
mov eax,123 ;把123存入eax寄存器中
cmp eax,100 ;把eax寄存器的值同100进行比较
jle short @8 ;比100小时,跳转到@8标签
call _MySub1 ;调用MySub1函数
@8 cmp eax,50 ;把eax寄存器的值同50进行比较
jge short @10 ;大于50时,跳转到@10标签
call _MySub2 ;调用MySub2函数
jmp short @11 ;跳转到@11标签
@10 call _Mysub3 ;调用MySub3函数
@11 pop ebp
ret
_MyFunc endp
// eax寄存器表示的是变量a
cmp:用来比较的指令。
比较结果被保存在标志寄存器。
三种跳转指令:
- jle(jump on less or equal) 比较结果小时跳转
- jge(jump on greater or equal):比较结果大时跳转
- jmp:不管比较结果怎样都无条件跳转
C语嫣中goto相当于jmp指令