【C数据结构与算法】用系统堆栈,汇编代码,寄存器来理解C形参和实参

说说C语言中的形参和实参。

      据我的学习,形参和实参 1.个数相等;

                                            2.位置关系对应;

                                            3.类型一致;

                                            4.效果等于   值传递(实参表达式的值,复制一份,传递给形参)

      

      下面有个例子:实现一个交换两个整形变量的交换

#include <stdio.h>

void exchange (int,int);

void exchange (int *one,int *another)
{
    int tmp;
    tmp = *one;
    *one = *another;
    *another = tmp;
}



int main ()
{
    int num1;
    int num2;
    scanf ("%d",&num1);
    scanf ("%d",&num2);
    exchange (&num1,&num2);
    printf ("结果为:%d,%d\n",num1,num2);
    return 0;
}

从内存角度分析:

 

  1. num1 入栈,栈顶指针上移4字节;
  2. num2 入栈,栈顶指针上移4字节;
  3. 保存主函数现场信息,栈顶指针上移8字节;
  4. 将num2的地址信息入栈,栈顶指针上移4字节;
  5. 将num1的地址信息入栈,栈顶指针上移4字节; 
  6. 将num1的地址信息(首地址)传递给one;  
  7. 将num2的地址信息(首地址)传递给another;  !!!实参表达式计算出的值在堆栈里所占的空间就是形参变量的空间,形参和实参是值传递的关系,形参所占的空间就是占用了之前系统分配给实参的空间!
  8. tmp入栈,栈顶指针上移4字节;
  9. 将one所指向空间的值(num1的值)赋值给tmp;
  10. 将another所指向空间的值(num2的值)赋值给one所指向的空间;
  11. 将tmp赋值给another所指向的空间;
  12. 栈顶指针下移12个字节,即指向了 主地址现场信息 ;
  13. 继续执行主函数。。。输出。。                                         

      在此提一点:形参与实参是单向传递,对行参变量的任何修改都不会更改实参表达式原本的值!

     

      如果继续向本质去探寻,就不得不扯上关于汇编的内容,命令行编译该程序(cl /FAs exchange.c),生成格式为asm的文件,打开该文件:

	TITLE	exchange.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	_exchange
_TEXT	SEGMENT
_one$ = 8
_another$ = 12
_tmp$ = -4
_exchange PROC NEAR

; 6    : {

	push	ebp                 
	mov	ebp, esp              
	push	ecx
	 
	; ebp值入栈(将main() ebp保护起来)
	; 再次形成“空栈”,就是exchange()的空栈

; 7    : 	int tmp;
; 8    : 	tmp = *one;

	mov	eax, DWORD PTR _one$[ebp]        ; mov eax, 4B ebp[4]
	mov	ecx, DWORD PTR [eax]             
	mov	DWORD PTR _tmp$[ebp], ecx

; 9    : 	*one = *another;

	mov	edx, DWORD PTR _one$[ebp]
	mov	eax, DWORD PTR _another$[ebp]
	mov	ecx, DWORD PTR [eax]
	mov	DWORD PTR [edx], ecx

; 10   : 	*another = tmp;

	mov	edx, DWORD PTR _another$[ebp]
	mov	eax, DWORD PTR _tmp$[ebp]
	mov	DWORD PTR [edx], eax

; 11   : 	}

	mov	esp, ebp
	pop	ebp    ; 用栈顶当前的值(main() ebp)赋值给ebp,并esp--4
	; 使ebp回到main函数的ebp状态
	ret	0
	; mov eip,栈顶
	; 恢复CPU运行顺序,回到主函数被中断时的状态
 

_exchange ENDP
_TEXT	ENDS
PUBLIC	_main                      ;主函数
EXTRN	_printf:NEAR
EXTRN	_scanf:NEAR
_DATA	SEGMENT                     ;数据区
$SG347	DB	'%d', 00H
	ORG $+1
$SG348	DB	'%d', 00H
	ORG $+1
$SG349	DB	0bdH, 0e1H, 0b9H, 0fbH, 0ceH, 0aaH, ':%d,%d', 0aH, 00H
_DATA	ENDS
_TEXT	SEGMENT
_num1$ = -4                  
_num2$ = -8
_main	PROC NEAR

; 14   : {

	push	ebp              ; 将ebp值入栈,esp会-4
	mov	ebp, esp           ; 将esp的值赋值给ebp;   So,esp和ebp此时指向同一个空间,即 形成了一个空栈,每一个函数都有
	sub	esp, 8             ; 将esp的值-8,  意思是将栈顶指针抬高8B,意味着栈顶指针和栈底指针之间有了8B的间隔,8B应该为两个int空间;就是num1和num2
	                       ; 调用主函数的“函数”(操作系统的代码),他的ebp被保护起来(通过push和mov),原栈底空间被保护起来
; 15   : 	int num1;
; 16   : 	int num2;
; 17   : 	scanf ("%d",&num1);

	lea	eax, DWORD PTR _num1$[ebp]          ;-4[ebp]    3[a] = a[3] 
	  
	; lea eax,DWORD PTR -4[ebp]  <=> lea eax,DWORD PTR  ebp[-4]
	; eax存入以ebp-4为首地址的int空间
	; DWORD_PTR就是int的对应
	; 系统堆栈向低端增长,意思是,入栈(push)会--esp,而出栈(pop)会++esp
	; esp:系统堆栈栈顶指针。ebp:系统堆栈栈底指针
	
	; ebp这个栈底指针的上方,隔4B空间,是主函数的局部变量num1的空间
	
	; 再看主函数局部变量num2,(_num2$ = -8),同理,他离栈底指针更远,间隔8B空间
	; !!!这就说明栈底和第一个局部变量(num1)之间存在一个4B空间!!!           
	
	; 5.  这4B空间就是num1的空间!!!  因为系统堆栈中越往上地址值越小,越往下地址值越大!
	
	push	eax
	push	OFFSET FLAT:$SG347
	call	_scanf
	add	esp, 8

; 18   : 	scanf ("%d",&num2);

	lea	ecx, DWORD PTR _num2$[ebp]
	push	ecx
	push	OFFSET FLAT:$SG348
	call	_scanf
	add	esp, 8

; 19   : 	
; 20   : 	exchange (&num1,&num2);               ;   调用函数

	lea	edx, DWORD PTR _num2$[ebp]
	push	edx
	lea	eax, DWORD PTR _num1$[ebp]
	push	eax
	; 将num2和num1首地址入栈;
	
	call	_exchange                
	               
	; call指令内部会执行push eip 的操作!!! 保护eip的值,以便返回时能继续执行主调函数的下一条指令!
	; 这条指令就是下面的add esp, 8; 接着call会执行mov eip, exchange; exchange就是子函数exchange的首地址常量
	; CPU将取出exchange函数的第一条指令开始执行,即,开始执行子函数exchange
	; 这里的call由于执行的是push eip;所以,此时esp又会-4
	
	add	esp, 8   ; esp下落8字节,此时正好回到调用前状态

; 21   : 	printf ("结果为:%d,%d\n",num1,num2);

	mov	ecx, DWORD PTR _num2$[ebp]
	push	ecx
	mov	edx, DWORD PTR _num1$[ebp]
	push	edx
	push	OFFSET FLAT:$SG349            ; 字符串常量的本质是该字符串常量的首地址常量
	call	_printf
	add	esp, 12					; 0000000cH

; 22   : 	return 0;

	xor	eax, eax

; 23   : }

	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END

; ebp值入栈(将main() ebp保护起来)
; 再次形成“空栈”,就是exchange()的空栈

; 7    : 	int tmp;
; 8    : 	tmp = *one;

	mov	eax, DWORD PTR _one$[ebp]        ; mov eax, 4B ebp[4]
	mov	ecx, DWORD PTR [eax]             
	mov	DWORD PTR _tmp$[ebp], ecx

; 9    : 	*one = *another;

	mov	edx, DWORD PTR _one$[ebp]
	mov	eax, DWORD PTR _another$[ebp]
	mov	ecx, DWORD PTR [eax]
	mov	DWORD PTR [edx], ecx

; 10   : 	*another = tmp;

	mov	edx, DWORD PTR _another$[ebp]
	mov	eax, DWORD PTR _tmp$[ebp]
	mov	DWORD PTR [edx], eax

; 11   : 	}

	mov	esp, ebp
	pop	ebp    ; 用栈顶当前的值(main() ebp)赋值给ebp,并esp--4
	; 使ebp回到main函数的ebp状态
	ret	0
	; mov eip,栈顶
	; 恢复CPU运行顺序,回到主函数被中断时的状态
 

_exchange ENDP
_TEXT	ENDS
PUBLIC	_main                      ; 主函数
EXTRN	_printf:NEAR
EXTRN	_scanf:NEAR
_DATA	SEGMENT                      ; 数据区
$SG347	DB	'%d', 00H
	ORG $+1
$SG348	DB	'%d', 00H
	ORG $+1
$SG349	DB	0bdH, 0e1H, 0b9H, 0fbH, 0ceH, 0aaH, ':%d,%d', 0aH, 00H
_DATA	ENDS
_TEXT	SEGMENT
_num1$ = -4                  
_num2$ = -8
_main	PROC NEAR

; 14   : {

	push	ebp              ; 将ebp值入栈,esp会-4
	mov	ebp, esp           ; 将esp的值赋值给ebp;   So,esp和ebp此时指向同一个空间,即 形成了一个空栈,每一个函数都有
	sub	esp, 8             ; 将esp的值-8,  意思是将栈顶指针抬高8B,意味着栈顶指针和栈底指针之间有了8B的间隔,8B应该为两个int空间;就是num1和num2
	                       ; 调用主函数的“函数”(操作系统的代码),他的ebp被保护起来(通过push和mov),原栈底空间被保护起来
; 15   : 	int num1;
; 16   : 	int num2;
; 17   : 	scanf ("%d",&num1);

	lea	eax, DWORD PTR _num1$[ebp]          ; -4[ebp]    3[a] = a[3] 
	  
	; lea eax,DWORD PTR -4[ebp]  <=> lea eax,DWORD PTR  ebp[-4]
	; eax存入以ebp-4为首地址的int空间
	; DWORD_PTR就是int的对应
	; 系统堆栈向低端增长,意思是,入栈(push)会--esp,而出栈(pop)会++esp
	; esp:系统堆栈栈顶指针。ebp:系统堆栈栈底指针
	
	; ebp这个栈底指针的上方,隔4B空间,是主函数的局部变量num1的空间
	
	; 再看主函数局部变量num2,(_num2$ = -8),同理,他离栈底指针更远,间隔8B空间
	; !!!这就说明栈底和第一个局部变量(num1)之间存在一个4B空间!!!           
	
	; 5.  这4B空间就是num1的空间!!!  因为系统堆栈中越往上地址值越小,越往下地址值越大!
	
	push	eax
	push	OFFSET FLAT:$SG347
	call	_scanf
	add	esp, 8

; 18   : 	scanf ("%d",&num2);

	lea	ecx, DWORD PTR _num2$[ebp]
	push	ecx
	push	OFFSET FLAT:$SG348
	call	_scanf
	add	esp, 8

; 19   : 	
; 20   : 	exchange (&num1,&num2);               //   调用函数

	lea	edx, DWORD PTR _num2$[ebp]
	push	edx
	lea	eax, DWORD PTR _num1$[ebp]
	push	eax
	; 将num2和num1首地址入栈;
	
	call	_exchange                
	               
	; call指令内部会执行push eip 的操作!!! 保护eip的值,以便返回时能继续执行主调函数的下一条指令!
	; 这条指令就是下面的add esp, 8; 接着call会执行mov eip, exchange; exchange就是子函数exchange的首地址常量
	; CPU将取出exchange函数的第一条指令开始执行,即,开始执行子函数exchange
	; 这里的call由于执行的是push eip;所以,此时esp又会-4
	
	add	esp, 8   ; esp下落8字节,此时正好回到调用前状态

; 21   : 	printf ("结果为:%d,%d\n",num1,num2);

	mov	ecx, DWORD PTR _num2$[ebp]
	push	ecx
	mov	edx, DWORD PTR _num1$[ebp]
	push	edx
	push	OFFSET FLAT:$SG349            ; 字符串常量的本质是该字符串常量的首地址常量
	call	_printf
	add	esp, 12					; 0000000cH

; 22   : 	return 0;

	xor	eax, eax

; 23   : }

	mov	esp, ebp
	pop	ebp
	ret	0
_main	ENDP
_TEXT	ENDS
END

 

建议根据汇编代码和我给的理解和注释画图理解!
 

全部评论

相关推荐

面试摇了我吧:啊哈哈面试提前五个小时发,点击不能参加就是放弃
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务