数据结构与算法之从汇编语言角度探讨全局变量和局部变量,形参(数据段和系统堆栈)

关于局部变量和形参变量我们将从汇编的角度来讨论这个问题
首先看c语言代码:

#include <stdio.h>

int globolVar = 100;	// 100对应的十六机制为64H

int functionOne(int formalVarInt, int formalVArShort);

int functionOne(int formalVarInt, int formalVArShort) {
	static int staticVarInt = 20;

	staticVarInt += formalVarInt + formalVArShort;
	++globolVar;

	return globolVar + staticVarInt;
}

int main()
{
	int mainLocalVar = 30;
	int var2 = 17;

	mainLocalVar = functionOne(mainLocalVar, var2);

	return 0;
}

在该程序中我们定义了一个全局变量globolvar,一个静态变量staticVarint,以及局部变量mainLocalvar和var2,定义了一个functionOne的函数,其具体实现的功能看代码应该不难理解。接下来我们把该文件转换成.asm文件,即汇编文件。

	TITLE	aboutSysStack.c
	.386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT	SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT	ENDS
_DATA	SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA	ENDS
CONST	SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST	ENDS
_BSS	SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS	ENDS
_TLS	SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS	ENDS
FLAT	GROUP _DATA, CONST, _BSS
	ASSUME	CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC	_globolVar
_DATA	SEGMENT
_globolVar DD	064H
_?staticVarInt@?1??functionOne@@9@9 DD 014H
_DATA	ENDS
PUBLIC	_functionOne
_TEXT	SEGMENT
_formalVarInt$ = 8
_formalVArShort$ = 12
_functionOne PROC NEAR
; File aboutSysStack.c
; Line 7
	push	ebp
	mov	ebp, esp
; Line 10
	mov	eax, DWORD PTR _formalVarInt$[ebp]
	add	eax, DWORD PTR _formalVArShort$[ebp]
	mov	ecx, DWORD PTR _?staticVarInt@?1??functionOne@@9@9
	add	ecx, eax
	mov	DWORD PTR _?staticVarInt@?1??functionOne@@9@9, ecx
; Line 11
	mov	edx, DWORD PTR _globolVar
	add	edx, 1
	mov	DWORD PTR _globolVar, edx
; Line 13
	mov	eax, DWORD PTR _globolVar
	add	eax, DWORD PTR _?staticVarInt@?1??functionOne@@9@9
; Line 14
	pop	ebp
	ret	0
_functionOne ENDP
_TEXT	ENDS
PUBLIC	_main
_TEXT	SEGMENT
_mainLocalVar$ = -4
_var2$ = -8
_main	PROC NEAR
; Line 17
	push	ebp
	mov	ebp, esp
	sub	esp, 8
; Line 18
	mov	DWORD PTR _mainLocalVar$[ebp], 30	; 0000001eH
; Line 19
	mov	DWORD PTR _var2$[ebp], 17		; 00000011H
; Line 21
	mov	eax, DWORD PTR _var2$[ebp]
	push	eax
	mov	ecx, DWORD PTR _mainLocalVar$[ebp]
	push	ecx
	call	_functionOne
	add	esp, 8
	mov	DWORD PTR _mainLocalVar$[ebp], eax
; Line 23
	xor	eax, eax
; Line 24
	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END

汇编文件由数据段DATA SEGMENT和代码段 TEXT SEGMENT构成
首先看数据段的内容:

_DATA	SEGMENT
_globolVar DD	064H
_?staticVarInt@?1??functionOne@@9@9 DD 014H  // 100对应的十六机制为64H
_DATA	ENDS

所以不难看出全局变量和静态存储类变量是在数据段中就已经存在的,这就说明这种变量不是随函数的调用而分配空间,随函数的结束而释放空间。
看代码段:

_formalVarInt$ = 8
_formalVArShort$ = 12

大家感到奇怪的是这两个变量的值不应该是按主函数值传递过来的值嘛 怎么出来这两个陌生的值,其实这两个值并不代表的是他们本身空间储存的值,而是“偏移量”,先不对此做过多的解释。
接下来介绍两个寄存器,ebp和esp,分别是栈底指针和栈顶指针。
看主函数区域:

push	ebp
	mov	ebp, esp
	sub	esp, 8

这里 push ebp是将ebp的值入栈,对原ebp的保护。然后此时栈顶指针esp自动指到栈顶即在堆栈空间中上升四个字节,此时mov ebp ,esp是将esp的值赋值给ebp此时栈底指针和栈顶指针都指向堆栈中的同一个空间,即开辟了一块新的系统堆栈空间来供函数使用。然后sub esp是将esp向上移了八个字节的空间,即ebp和esp之间存在着八个字节的空间。

_mainLocalVar$ = -4
_var2$ = -8

这里这两个局部变量的值并不是30和17,其实是一种“偏移量”,先不解释。

	mov	DWORD PTR _mainLocalVar$[ebp], 30	; 0000001eH  

	mov	DWORD PTR _var2$[ebp], 17		; 00000011H

这两条语句的含义是指 将30赋值给以ebp所指向的空间为底,以mainLocalVar的值-4为偏移量向上四字节的空间,同样第二条语句是将17赋值给ebp所指向的空间为底,向上偏移量为8的空间。
接下来简单介绍下两个寄存器:eax和ecx。

	mov	eax, DWORD PTR _var2$[ebp]
	push	eax
	mov	ecx, DWORD PTR _mainLocalVar$[ebp]
	push	ecx
	call	_functionOne
	add	esp, 8
	mov	DWORD PTR _mainLocalVar$[ebp], eax

这里的语句含义是说 将var2的值赋值给eax,再push eax将eax入栈,然后将ecx入栈,然后call functionOne含义是将该语句的下一个语句的首地址入栈,记录被调函数执行完成后,计算机下一步执行的指令是哪一句。所以在系统堆栈里又多了四字节的地址,接下来我们看被调函数

formalVarInt$ = 8
_formalVArShort$ = 12
_functionOne PROC NEAR
; File aboutSysStack.c
; Line 7
	push	ebp
	mov	ebp, esp
; Line 10
	mov	eax, DWORD PTR _formalVarInt$[ebp]
	add	eax, DWORD PTR _formalVArShort$[ebp]
	mov	ecx, DWORD PTR _?staticVarInt@?1??functionOne@@9@9
	add	ecx, eax
	mov	DWORD PTR _?staticVarInt@?1??functionOne@@9@9, ecx
; 

这里formalVarInt$ = 8
_formalVArShort$ = 12代表的也是偏移量,与开始的
_mainLocalVar$ = -4
_var2$ = -8
方向相反,先不管。
开始,又将ebp的值入系统堆栈,保护了原ebp,然后通过mov将ebp和esp指向同一个系统堆栈空间,也验证了每次函数执行都 将开辟新的系统堆栈空间。然后将以ebp为底,偏移量为8的空间的值赋值给eax,这里为什么是8呢因为有一个call指令下占用的返回语句地址指针和一个存储原ebp的空间 ,所以此时eax的值等于30,即完成了主函数传入的实参mainLocalVar与子函数formalVarInt完成了值传递,然后将_formalVArShort$[ebp]的值和eax相加并赋值给eax,然后将静态存储类变量staticVarInt的值赋值给ecx,将eax和ecx相加,相加的值赋值给ecx,然后将ecx的值赋值给staticVarInt,即完成 了子函数中

staticVarInt += formalVarInt + formalVArShort;

该条语句的相关操作。

Line 11
	mov	edx, DWORD PTR _globolVar
	add	edx, 1
	mov	DWORD PTR _globolVar, edx
; Line 13
	mov	eax, DWORD PTR _globolVar
	add	eax, DWORD PTR _?staticVarInt@?1??functionOne@@9@9
; Line 14
	pop	ebp
	ret	0

此处将globolVar的值赋值给edx,并将一加到edx中,然后将edx的赋值给globolVar。
即完成了++globolVar;
然后将globolVar的值赋值给eax,然后又将staticVar累加到eax中,然后将释放出ebp的值,
汇编指令 :ret是将返回到主调函数,其内部操作如下:
1.取得栈顶元素的值
2.并赋值给IP寄存器

所以此时call语句在系统堆栈空间中存储的返回指针已出栈。此时将回到主调函数中

call	_functionOne
	add	esp, 8
	mov	DWORD PTR _mainLocalVar$[ebp], eax
; Line 23
	xor	eax, eax
; Line 24
	mov	esp, ebp
	pop	ebp
	ret	0

此时将回到call的下一条语句,将esp向下移动八个字节即释放掉形参所占用的空间,将eax的值赋值给mainLocalVar ,即将子函数的返回值赋值给mainLocalVar
xor是做一个异或运算 即return 0
后将esp指向ebp的空间 再将ebp的值从堆栈中释放,也就对应了函数的调用无论是主函数还是子函数都将占用系统堆栈空间。就算当函数的内部无语句,最低要 占用八个字节的系统堆栈空间用以存放原ebp的值的堆栈和空间和存放指令返回地址的空间。
所以,递归也是函数调用的一种,因为函数调用需要消耗系统堆栈空间,而系统堆栈空间又十分宝贵,所以无度的递归,会使得系统堆栈空间溢出,这将导致系统崩溃。

计算机存储体系

1.海量外存
2.外存
3.内存
4.高速缓冲区
5.寄存器

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张嘉書

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值