突破编程_C++_面试(基础知识(3))

面试题5:函数调用的过程

C++ 中函数的调用包含参数入栈、函数跳转、保护现场、回复现场等过程,重点过程如下:
(1)将函数的参数压入栈中,从右至左压入。
(2)调用函数时,将当前程序的执行位置(即返回地址)压入栈中。
(3)将函数的栈帧(也称为活动记录)压入栈中。栈帧包含了函数的局部变量、函数返回值、函数的上一级调用者的栈帧指针等信息。
(4)执行函数体内的语句,包括局部变量的声明和初始化、函数体语句的执行等。
(5)函数执行完毕后,将函数的返回值保存在寄存器中(或者栈中)。
(6)弹出函数的栈帧,并将返回值传递给上一级函数。
(7)将返回地址弹出栈中,程序跳转到该地址继续执行。
以如下代码为例( 64 位程序):

#include 

int add(int a, int b)
{
	int sum = a + b;
	return sum;
}

int main()
{
	int sum = add(1, 2);

	return 0;
}

首先给 main() 函数的第一行 int sum = add(1, 2); 打上断点,调试运行程序。
程序暂停后,查看当前汇编代码( VS2017 查看方法:右击当前代码页,选择转到反汇编):

int main()
{
00007FF67D8AA630  push        rbp  
00007FF67D8AA632  push        rdi  
00007FF67D8AA633  sub         rsp,108h  
00007FF67D8AA63A  lea         rbp,[rsp+20h]  
00007FF67D8AA63F  mov         rdi,rsp  
00007FF67D8AA642  mov         ecx,42h  
00007FF67D8AA647  mov         eax,0CCCCCCCCh  
00007FF67D8AA64C  rep stos    dword ptr [rdi]  
00007FF67D8AA64E  lea         rcx,[__81FC6F77_main2@cpp (07FF67D9E41D7h)]  
00007FF67D8AA655  call        __CheckForDebuggerJustMyCode (07FF67D874108h)  
	int sum = add(1, 2);
00007FF67D8AA65A  mov         edx,2  
00007FF67D8AA65F  mov         ecx,1  
00007FF67D8AA664  call        add (07FF67D87584Bh)  
00007FF67D8AA669  mov         dword ptr [sum],eax  

	return 0;
00007FF67D8AA66C  xor         eax,eax  
}

在汇编代码中,程序暂停在第 14 行(00007FF67D8AA65A mov edx,2)。后面的两行是传入参数的过程,其中,edx是数据寄存器,常用于存储一些大于 AX 寄存器的 16 位数和 32 位数的运算中的高位数。在函数调用中, edx 寄存器用于存储第一个参数值。ecx是计数寄存器,常用于存储循环计数器和移位操作的计数器。在函数调用中, ecx 寄存器用于存储第二个参数值。通过这两行传入的值可以看出,调用函数时,参数入栈时从右往左。
汇编行00007FF67D8AA664 call add (07FF67D87584Bh)用于跳转到待调用的函数内,但这里需要注意的是,地址07FF67D87584Bh并不是待调用的函数的地址,该代码会执行到下面这一行:

00007FF67D87584B  jmp         add (07FF67D8AA5C0h)  

这里的地址07FF67D8AA5C0h才是真正待调用函数的地址。下面即进入被调用函数内部:

int add(int a, int b)
{
00007FF67D8AA5C0  mov         dword ptr [rsp+10h],edx  
00007FF67D8AA5C4  mov         dword ptr [rsp+8],ecx  
00007FF67D8AA5C8  push        rbp  
00007FF67D8AA5C9  push        rdi  
00007FF67D8AA5CA  sub         rsp,108h  
00007FF67D8AA5D1  lea         rbp,[rsp+20h]  
00007FF67D8AA5D6  mov         rdi,rsp  
00007FF67D8AA5D9  mov         ecx,42h  
00007FF67D8AA5DE  mov         eax,0CCCCCCCCh  
00007FF67D8AA5E3  rep stos    dword ptr [rdi]  
00007FF67D8AA5E5  mov         ecx,dword ptr [rsp+128h]  
00007FF67D8AA5EC  lea         rcx,[__81FC6F77_main2@cpp (07FF67D9E41D7h)]  
00007FF67D8AA5F3  call        __CheckForDebuggerJustMyCode (07FF67D874108h)  
	int sum = a + b;
00007FF67D8AA5F8  mov         eax,dword ptr [b]  
00007FF67D8AA5FE  mov         ecx,dword ptr [a]  
00007FF67D8AA604  add         ecx,eax  
00007FF67D8AA606  mov         eax,ecx  
00007FF67D8AA608  mov         dword ptr [sum],eax  
	return sum;
00007FF67D8AA60B  mov         eax,dword ptr [sum]  
}

这段汇编代码的第 2 行到第 15 行之间是对该函数的栈初始化工作,由编译器自动添加。其中 rsp ( 32 位程序中是 esp ) 、rbp ( 32 位程序中是 ebp )、rdi ( 32 位程序中是 edi )是常用的寄存器:
rsp 为栈指针,常用来指向栈顶。上面汇编代码中第 6 行00007FF67D8AA5CA sub rsp,108h的意思是将栈顶指针往上移动 108h Byte。这个区域为间隔空间,将被调用的 add 函数与 main 函数的栈区域隔开一段距离,同时还要预留出存储局部变量的内存区域。
rbp 为基址指针,常用来指向栈底。
rdi 为目的变址寄存器。
上面汇编代码的第 17 行到第 21 行之间是进行两数相加的逻辑操作。
执行到第最后一行后打开寄存器查看器( VS2017 查看方法:调试–>窗口–>寄存器),可以查看到如下值:

RAX = 0000000000000003 RBX = 0000000000000000 RCX = 0000000000000003 RDX = 0000000000000002 RSI = 0000000000000000 RDI = 0000005BD30FFA58 R8  = 0000020993014F70 R9  = 0000005BD30FF954 R10 = 0000000000000013 R11 = 00000209930242E0 R12 = 0000000000000000 R13 = 0000000000000000 R14 = 0000000000000000 R15 = 0000000000000000 RIP = 00007FF67D8AA60B RSP = 0000005BD30FF950 RBP = 0000005BD30FF970 EFL = 00000206 

0x0000005BD30FF974 = 00000003 

查看寄存器 RDI 的内存值( VS2017 查看方法:调试–>窗口–>内存->内存1):

0000005bd30ffb78 0000005bd30ffa90 00007ff67d8aa669 00007ff600000001 cccccccc00000002 cccccccccccccccc cccccccccccccccc cccccccccccccccc cccccccccccccccc cccccccccccccccc cccccccccccccccc

其中第三个值 00007ff67d8aa669 是 main 函数中调用该函数后的下一行汇编代码。
至此,整个调用过程结束。

面试题6:怎样判断两个浮点数是否相等

由于浮点数存入时有可能因为四舍五入而造成精度损失,所以两个浮点数直接用==操作符进行比较很可能会得到不符合预期的结果。
浮点数的比较应该使用如下方式:
对于浮点数而言比较合适的精度为:0.000001
对于双精度浮点数而言比较合适的精度为:0.0000000000000001
因此可以定义两个宏:

#define ACCURACY_F 1e-6
#define ACCURACY_D 1e-16

判断浮点数是否等于 0 :
float 类型:if(fabs(f) <= ACCURACY_F );
double 类型:if(fabs(d) <= ACCURACY_D);
判断两个浮点数是否相等:
float 类型:if(fabs(f1 - f2) <= ACCURACY_F);
double 类型:if(fabs(d1 - d2) <= ACCURACY_D);

你可能感兴趣的:(突破编程_C++_面试,面试,c++)