C++关于返回引用和非引用的区别
这几天刚好遇到一个题目。我们跳过背景,直接切入正题。
template<class T>
class HyperVectorIterator : public std::iterator< std::random_access_iterator_tag, T >;
这是类的定义,这个类的目的是继承iterator,然后完成相应的++, --, <, ==等相应符号的重载,作为HyperVector类的迭代器。HyperVector类是一个自定义的容器类,支持随机访问,这里先不多介绍此类。下面我们拿自减一的操作符重载来实验:
// An highlighted block
template<class T>
HyperVectorIterator<T> HyperVectorIterator<T>::operator--()
{
--(this->m_pos);
return HyperVectorIterator(this->m_pContainer, this->m_pos);
}
// An highlighted block
template<class T>
HyperVectorIterator<T>& HyperVectorIterator<T>::operator--()
{
--(this->m_pos);
return HyperVectorIterator(this->m_pContainer, this->m_pos);
}
几乎一样的代码,唯一不同的是下面这个类操作符重载返回的是一个引用。有什么区别呢?
如果抛开底层汇编代码,那么直观的理解应该是第一份代码返回对象的时候做了创建对象、对象拷贝、释放对象的操作,而第二份代码返回对象的时候仅做了创建对象一步操作。但不是很能理解的是:函数在结束的时候,应该会释放函数内的局部变量的内存空间,那么返回的引用会不会被销毁?现实是C++使用了那么多年,如果真发生这样的bug,应该轮不到被我发现。所以问题应该出自于自己理解的偏差。
我很懒,不是很喜欢翻书。但这个问题一直积压着又很难受,所以,我尝试往下再看一层,汇编层。
template<class T>
HyperVectorIterator<T> HyperVectorIterator<T>::operator--()
{
00419390 push ebp
00419391 mov ebp,esp
00419393 sub esp,0CCh
00419399 push ebx
0041939A push esi
0041939B push edi
0041939C push ecx
0041939D lea edi,[ebp-0CCh]
004193A3 mov ecx,33h
004193A8 mov eax,0CCCCCCCCh
004193AD rep stos dword ptr es:[edi]
004193AF pop ecx
004193B0 mov dword ptr [this],ecx
--(this->m_pos);
004193B3 mov eax,dword ptr [this]
004193B6 mov ecx,dword ptr [eax+4]
004193B9 sub ecx,1
004193BC mov edx,dword ptr [this]
004193BF mov dword ptr [edx+4],ecx
return HyperVectorIterator(this->m_pContainer, this->m_pos);
004193C2 mov eax,dword ptr [this]
004193C5 mov ecx,dword ptr [eax+4]
004193C8 push ecx
004193C9 mov edx,dword ptr [this]
004193CC mov eax,dword ptr [edx]
004193CE push eax
004193CF mov ecx,dword ptr [ebp+8]
004193D2 call HyperVectorIterator<float>::HyperVectorIterator<float> (0411A82h)
004193D7 mov eax,dword ptr [ebp+8]
}
004193DA pop edi
004193DB pop esi
004193DC pop ebx
004193DD add esp,0CCh
004193E3 cmp ebp,esp
004193E5 call __RTC_CheckEsp (04114C9h)
004193EA mov esp,ebp
004193EC pop ebp
004193ED ret 4
(先补充一个知识:call指令返回值的时候都是用ax寄存器来保存的)
返回非引用的时候,004193D7地址的指令果然是做了一次对象的拷贝。而且可以看到保存到了ebp+8的地址处,ebp应该函数内临时变量分配的基址,局部变量保存的位置应该都比ebp来的小,那么这个比ebp大的ebp+8位置的地方是哪里呢?应该是上一个调用者的分配到的内存空间。这也很合理,不然在函数退出时局部变量都被销毁了还怎么拿到新分配的对象呢?
再看下面一段代码:
template<class T>
HyperVectorIterator<T>& HyperVectorIterator<T>::operator--()
{
00419390 push ebp
00419391 mov ebp,esp
00419393 sub esp,0DCh
00419399 push ebx
0041939A push esi
0041939B push edi
0041939C push ecx
0041939D lea edi,[ebp-0DCh]
004193A3 mov ecx,37h
004193A8 mov eax,0CCCCCCCCh
004193AD rep stos dword ptr es:[edi]
004193AF pop ecx
004193B0 mov dword ptr [this],ecx
--(this->m_pos);
004193B3 mov eax,dword ptr [this]
004193B6 mov ecx,dword ptr [eax+4]
004193B9 sub ecx,1
004193BC mov edx,dword ptr [this]
004193BF mov dword ptr [edx+4],ecx
return HyperVectorIterator(this->m_pContainer, this->m_pos);
004193C2 mov eax,dword ptr [this]
004193C5 mov ecx,dword ptr [eax+4]
004193C8 push ecx
004193C9 mov edx,dword ptr [this]
004193CC mov eax,dword ptr [edx]
004193CE push eax
004193CF lea ecx,[ebp-0D8h]
004193D5 call HyperVectorIterator<float>::HyperVectorIterator<float> (0411A82h)
}
004193DA pop edi
004193DB pop esi
004193DC pop ebx
004193DD add esp,0DCh
004193E3 cmp ebp,esp
004193E5 call __RTC_CheckEsp (04114C9h)
004193EA mov esp,ebp
004193EC pop ebp
004193ED ret
在004193D5地址执行完call指令后什么都没做就进入函数退出前的恢复动作了!也就是返回引用的时候不会改变eax的值。临时对象一直在寄存器里面,没有进入过内存。
今天就写这么多,第一次写优快云的文章。这个问题其实是为另一个问题做铺垫的,大背景其实是写自己的迭代器,重载符号函数的时候返回引用还是非引用,会直接影响容器类对象调用sort方法时是否产生崩溃。