swap到底换没换

本文详细分析了C语言中swap(int*x,int*y)函数的工作原理,通过比较与swap(intx,inty)的不同,揭示了实参和形参的交换机制以及底层汇编指令的作用,强调了函数调用在内存管理和功能实现中的关键作用。
摘要由CSDN通过智能技术生成

swap到底换没换

前言

最近在学习汇编语言,突然想到了当初刚学习C语言指针时老师用来做例子的swap(int* x, int* y)函数。正闲得没事干,于是突发奇想——这一次我要更加具体地从底层去解释这个函数,它为什么能够交换两个数字。

下文,我将通过分析汇编指令和堆栈图来分析swap函数。值得注意的是:下面的实验是在x86体系下完成的。

swap(int x, int y)为什么不能交换

C语言源代码:

//#include<stdio.h>
void swap(int x, int y) 
{
	int temp = x;
	x = y;
	y = temp;
}
int main() 
{
	int a = 3, b = 2;
	swap(a, b);
//	printf("%d %d", a, b);
	return 0;
}

毫无疑问,这样没办法交换a和b的值,自己想测试的读者把注释取消掉,跑一下就能很直观的看出来了。

C语言老师在讲的时候说,这个函数传入的是形参而不是实参,形参在swap函数中交换了而不是实参交换。

讲得很好,大家都懂,但是接下来我们看看汇编指令,和堆栈图来理解一下这句话

swap(int x, int y)函数的汇编指令:

注:

  • 为了使这个看起来更容易理解,我加入了其原本的C代码作为注释。
  • 下面的汇编指令由vs2022生成。与此同时我删去了汇编指令的地址,因为地址会根据程序载入内存时的地址而改变。虽然这样会使call,jmp等指令看着不具体,但是”无伤大雅“。
  • 这并不是所生成的全部汇编指令,我只截取了其中与swap函数有关的部分。
;int a = 3, b = 2
mov         dword ptr [a],3  
mov         dword ptr [b],2  
;swap(a, b)
mov         eax,dword ptr [b]  
push        eax  
mov         ecx,dword ptr [a]  
push        ecx  
call        swap (05A1078h)  
add         esp,8 
;Debug版本call修改的eip值是jmp指令执行处的地址,而这个jmp修改的eip值才是swap函数真正的地址
jmp 005A1740

;void swap(int x, int y) 
;{
push        ebp  
mov         ebp,esp  
sub         esp,0CCh  
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0Ch]  
mov         ecx,3  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi]  
mov         ecx,offset _6F1A6112_main@cpp (05AC000h)  
call        @__CheckForDebuggerJustMyCode@4 (05A130Ch)  
;int temp = x
mov         eax,dword ptr [x]  
mov         dword ptr [temp],eax  
;x = y
mov         eax,dword ptr [y]  
mov         dword ptr [x],eax  
;y = temp
mov         eax,dword ptr [temp]  
mov         dword ptr [y],eax  
;}
pop         edi  
pop         esi  
pop         ebx  
add         esp,0CCh  
cmp         ebp,esp  
call        __RTC_CheckEsp (05A1235h)  
mov         esp,ebp  
pop         ebp  
ret  

看起来很多,但是真正需要注意的指令只有几行,如下:

;int a = 3, b = 2
mov         dword ptr [a],3  
mov         dword ptr [b],2  
;int temp = x
mov         eax,dword ptr [x]  
mov         dword ptr [temp],eax  
;x = y
mov         eax,dword ptr [y]  
mov         dword ptr [x],eax  
;y = temp
mov         eax,dword ptr [temp]  
mov         dword ptr [y],eax  

解释:

  • [a],[b],[x],[y]是汇编语言中用语标识地址的一种方法(我猜测是vs反汇编的时候加的),[]里面的数是内存地址。dword ptr [a]表示a变量在内存中存储的地址,dword是a变量在内存中的数据宽度。

  • int a=3,b=2是在main函数中的代码,即a与b变量的值存储的地址是在main函数的栈空间中。而x,y,temp这三个变量的值存储在调用swap函数后为其分配的栈空间中,当函数执行交换指令时,只会交换swap栈空间中的x与y,并不影响该空间之外的a和b,所以a和b的值实际上不会交换。
    在这里插入图片描述

swap(int *x, int *y)为什么能交换

C语言源代码:

void swap(int* x, int* y) 
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
int main() 
{
	int a = 3, b = 2;
	swap(&a, &b);
	return 0;
}

先分析一下这段代码:这里参数的传入是a和b的地址,结合上文对swap(int x, int y)的分析,猜测mov eax,dword ptr [x]这一个汇编指令会真正的将参数的地址上的值写入EAX。这里的EAX是32位下一通用寄存器的名字,它的数据宽度是32位。

接下来我将通过反汇编结果和堆栈图来判断上述假设是否正确。

swap(int* x, int* y)函数的汇编指令:

;int a = 3, b = 2
mov         dword ptr [a],3  
mov         dword ptr [b],2  
	;swap(&a, &b)这里的传参传的是地址,是main函数中存储a和b变量的内存地址
lea         eax,[b]  
push        eax  
lea         ecx,[a]  
push        ecx  
call        swap (0571311h)  
add         esp,8  
;void swap(int* x, int* y) 
;{
push        ebp  
mov         ebp,esp  
sub         esp,0CCh  
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0Ch]  
mov         ecx,3  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi]  
mov         ecx,offset _6F1A6112_main@cpp (057C000h)  
call        @__CheckForDebuggerJustMyCode@4 (0571307h)  
;int temp = *x
mov         eax,dword ptr [x]  
mov         ecx,dword ptr [eax]  
mov         dword ptr [temp],ecx  
;*x = *y
mov         eax,dword ptr [x]  
mov         ecx,dword ptr [y]  
mov         edx,dword ptr [ecx]  
mov         dword ptr [eax],edx  
;*y = temp
mov         eax,dword ptr [y]  
mov         ecx,dword ptr [temp]  
mov         dword ptr [eax],ecx  
;}
pop         edi  
pop         esi  
pop         ebx  
add         esp,0CCh  
cmp         ebp,esp  
call        __RTC_CheckEsp (0571230h)  
mov         esp,ebp  
pop         ebp  
ret  

与交换功能有关的汇编指令如下:

;int a = 3, b = 2
mov         dword ptr [a],3  
mov         dword ptr [b],2  
;int temp = *x
mov         eax,dword ptr [x]  
mov         ecx,dword ptr [eax]  
mov         dword ptr [temp],ecx  
;*x = *y
mov         eax,dword ptr [x]  
mov         ecx,dword ptr [y]  
mov         edx,dword ptr [ecx]  
mov         dword ptr [eax],edx  
;*y = temp
mov         eax,dword ptr [y]  
mov         ecx,dword ptr [temp]  
mov         dword ptr [eax],ecx  

结合堆栈图解释:

在这里插入图片描述

  • 注意:黄色部分是main函数的栈空间,黄色部分以上是swap函数的栈空间。
  • 与上文第一张堆栈图不同的是函数入口处参数传递的分别是00F3FCB000F3FCBC,这两个参数是main栈空间中变量a和b的内存地址。在之后的操作中可以通过mov dword ptr [],eax操作直接修改main内存空间中a和b的值,从而达到交换目的。

总结

  1. 在大一时C语言老师所讲的实参与形参,可以理解为地址与值。实参是main函数栈空间中的某个值的地址,而形参指该地址上的值。
  2. 一个函数的完整调用最终会堆栈平衡,调用函数前后EBP,ESP寄存器的值会保持不变。在之前的程序中一共有两个函数main函数与swap函数,在main函数中调用完swap函数后堆栈会保持不变,在程序结束后main函数所占的栈空间也会被取消回到调用main前。
  3. 需要补充一点:我们以为swap函数没有交换两个数的值是站在main函数的角度看的,在main的栈空间中两个整数的值确实没变,但是在swap栈空间中,这两个值换了。所以说到底换没换,如换!
  4. 通过这次分析,我发现C语言的根本是函数的调用。通过不断调用函数,在内存中分配空间,实现函数功能,注销空间,来实现我们期望的功能。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值