目录
1. 主要区别
在函数传参中,传引用在本质上就是传地址,而传值调用是利用函数的栈空间进行传递,先将实体复制一份到栈内存,再通过内存栈传递参数。同时,通过传引用和传地址的任何对象,如果改动了任何属性,是直接改动原对象,而传值只会改变副本,不会改变原对象。
2. 例释(以Windows X64平台为例)
为了简单起见,定义一个结构体:
struct DataEntity
{
__int64 m = 0x1111111111111111; //为了便于观察内存,每一个字节都赋值
__int64 n = 0x2222222222222222;
__int64 Add()
{
return m + n;
}
};
定义三个函数,分别按传地址,传引用,传值方式传参:
void PrintPassByReference(DataEntity& data)
{
__int64 total = data.m + data.n;
}
void PrintPassByPtr(DataEntity* data)
{
__int64 total = data->m + data->n;
}
void PrintPassByValue(DataEntity data)
{
__int64 total = data.m + data.n;
}
调用代码:
DataEntity data;
PrintPassByReference(data);
PrintPassByPtr(&data);
PrintPassByValue(data);
汇编代码分析:
DataEntity data;
00007FF658FD50A3 lea rcx,[data]
00007FF658FD50A8 call DataEntity::DataEntity (07FF658E56F55h)
PrintPassByReference(data);
00007FF658FD50AD lea rcx,[data] ; 将 data 的地址存入寄存器rcx
00007FF658FD50B2 call PrintPassByReference (07FF658E56F5Ah)
进入 PrintPassByReference:
void PrintPassByReference(DataEntity& data)
{
00007FF658FD4FE0 mov qword ptr [rsp+8],rcx ;取出data的地址
00007FF658FD4FE5 sub rsp,18h
__int64 total = data.m + data.n;
00007FF658FD4FE9 mov rax,qword ptr [data] ;将data首地址送入rax
00007FF658FD4FEE mov rax,qword ptr [rax]
00007FF658FD4FF1 mov rcx,qword ptr [data]
00007FF658FD4FF6 add rax,qword ptr [rcx+8]
00007FF658FD4FFA mov qword ptr [rsp],rax
}
00007FF658FD4FFE add rsp,18h
00007FF658FD5002 ret
PrintPassByPtr(&data);
00007FF658FD50B7 lea rcx,[data] 将 data 的地址存入寄存器 rcx
00007FF658FD50BC call PrintPassByPtr (07FF658E56F69h)
;可以看出,在调用方式上与 PrintPassByReference 相同。
进入 PrintPassByPtr(&data);
void PrintPassByPtr(DataEntity* data)
{
00007FF658FD5010 mov qword ptr [rsp+8],rcx ;取出data的地址
00007FF658FD5015 sub rsp,18h
__int64 total = data->m + data->n;
00007FF658FD5019 mov rax,qword ptr [data]
00007FF658FD501E mov rax,qword ptr [rax]
00007FF658FD5021 mov rcx,qword ptr [data]
00007FF658FD5026 add rax,qword ptr [rcx+8]
00007FF658FD502A mov qword ptr [rsp],rax
}
00007FF658FD502E add rsp,18h
00007FF658FD5032 ret
从以上可以看出,PrintPassByReference 和 PrintPassByPtr 生成的汇编代码基本一致,二者都是直接传递数据的地址。
现在看看在进入PrintPassByValue之前的准备:
PrintPassByValue(data);
00007FF658FD50C1 lea rax,[rsp+40h] ;分配 40h 的栈空间
00007FF658FD50C6 lea rcx,[data] ;取得 data 的地址
00007FF658FD50CB mov rdi,rax ; 分配的空间地址送入寄存器rdi
00007FF658FD50CE mov rsi,rcx ; data 送入寄存器 rsi
00007FF658FD50D1 mov ecx,10h ;计数器值赋为 16 (data大小为16字节)
00007FF658FD50D6 rep movs byte ptr [rdi],byte ptr [rsi] ;按字节复制data到新的空间
00007FF658FD50D8 lea rcx,[rsp+40h] ;将栈空间地址送入rcx
00007FF658FD50DD call PrintPassByValue (07FF658E56F5Fh)
说明:以上红色部分为新分配栈空间并复制对象副本,这个过程对于 C++ 程序员是隐藏的。
进入 PrintPassByValue:
void PrintPassByValue(DataEntity data)
{
00007FF66BA05040 mov qword ptr [rsp+8],rcx ;取出栈中存储的data副本
00007FF66BA05045 sub rsp,18h
__int64 total = data.m + data.n;
00007FF66BA05049 mov rax,qword ptr [&data] ;这个data是栈中存储的副本
00007FF66BA0504E mov rax,qword ptr [rax]
00007FF66BA05051 mov rcx,qword ptr [&data]
00007FF66BA05056 add rax,qword ptr [rcx+8]
00007FF66BA0505A mov qword ptr [rsp],rax
}
00007FF66BA0505E add rsp,18h
00007FF66BA05062 ret
传值是对副本对应的内存进行操作,当然不会改变原对象。