关于后置++操作符中返回 Complex& 的使用已释放内存空间问题

来源:8-6 前置与后置操作符

奕帝传说_梦

2023-12-25

按照我的理解,对于 =,+=,*= 等操作符,由于被操作的对象是存在调用位置的栈空间中的,因此我们可以通过传递引用的方式避免 C++ 语言编译器触发拷贝构造的问题。
而对于 +,- 等操作符中,由于其返回值一般都是作为右值来使用,因此需要额外在调用位置的栈空间中单独开辟一段空间来存它(operator 函数的栈空间存储没有意义,在执行完毕后这段空间就被释放了),因此需要触发拷贝构造来特地创造一个空间来存储右值。
例如下面这个例子:

Complex& Complex::operator++ () {	//前置++
	_real ++ ;
	_image++;
	return *this;
};
Complex Complex::operator++ (int) {	//后置++
	Complex tmp(*this);
	_real++;
	_image++;
	return tmp;
};

对于前置++操作,由于我操作的 _real 和 _image 本来就是存在 main 的栈空间里的(而并非存在 operator++ 的栈空间里),即使函数执行完毕后也不会被释放,因此我可以直接传 Complex& 避免触发拷贝构造!
而对于后置++操作,由于 tmp 是存在 operator++ 的栈空间里的,如果这个函数执行完了,对应的栈空间就要被释放。因此需要再 main 的栈空间里创建一个额外的副本来存储这个 Complex 对象,才能将其给到 main 函数的变量中。
我的问题是:如果我不优化代码,让后置++操作也返回 Complex&,我运行结果是对的,是因为我使用了已经释放掉的 tmp 的空间来为 main 的变量赋值吗?
具体代码如下:

Complex& Complex::operator++ (int) {	//后置++
	Complex tmp(*this);
	_real++;
	_image++;
	return tmp;
};

这样写我的运行结果是对的,反汇编结果如下:

Complex& Complex::operator++ (int) {	//后置++
009C5FD0  push        ebp  
009C5FD1  mov         ebp,esp  
009C5FD3  sub         esp,64h  
009C5FD6  mov         eax,dword ptr [__security_cookie (09D9024h)]  
009C5FDB  xor         eax,ebp  
009C5FDD  mov         dword ptr [ebp-4],eax  
009C5FE0  push        ebx  
009C5FE1  push        esi  
009C5FE2  push        edi  
009C5FE3  mov         dword ptr [this],ecx  
009C5FE6  mov         ecx,offset _FE2B3486_Complex@cpp (09DC6F4h)  
009C5FEB  call        @__CheckForDebuggerJustMyCode@4 (09B20E5h)  
	Complex tmp(*this);
009C5FF0  mov         eax,dword ptr [this]  
009C5FF3  push        eax  
009C5FF4  lea         ecx,[tmp]  
009C5FF7  call        Complex::Complex (09B14D8h)  
	_real++;
009C5FFC  mov         eax,dword ptr [this]  
009C5FFF  movsd       xmm0,mmword ptr [eax+8]  
009C6004  addsd       xmm0,mmword ptr [__real@3ff0000000000000 (09D4D78h)]  
009C600C  mov         ecx,dword ptr [this]  
009C600F  movsd       mmword ptr [ecx+8],xmm0  
	_image++;
009C6014  mov         eax,dword ptr [this]  
009C6017  movsd       xmm0,mmword ptr [eax+10h]  
009C601C  addsd       xmm0,mmword ptr [__real@3ff0000000000000 (09D4D78h)]  
009C6024  mov         ecx,dword ptr [this]  
009C6027  movsd       mmword ptr [ecx+10h],xmm0  
	return tmp;
009C602C  lea         eax,[tmp]  
009C602F  mov         dword ptr [ebp-64h],eax  
009C6032  lea         ecx,[tmp]  
009C6035  call        Complex::~Complex (09B183Eh)  
009C603A  mov         eax,dword ptr [ebp-64h]  
};
009C603D  pop         edi  
009C603E  pop         esi  
009C603F  pop         ebx  
009C6040  mov         ecx,dword ptr [ebp-4]  
009C6043  xor         ecx,ebp  
009C6045  call        @__security_check_cookie@4 (09B173Ah)  
009C604A  mov         esp,ebp  
009C604C  pop         ebp  
009C604D  ret         4  
=============================上面是 operator++,下面是main==================================
	Complex e = d++;
009C5C64  push        0  
009C5C66  lea         ecx,[d]  
009C5C69  call        Complex::operator++ (09B23BAh)  
009C5C6E  push        eax  
009C5C6F  lea         ecx,[e]  
009C5C72  call        Complex::Complex (09B14D8h)  
009C5C77  mov         byte ptr [ebp-4],3  

可以看到,确实没有在return 时调用拷贝构造,而是直接把 [tmp] 给到了 eax,然后在 main 中直接把 eax 给压栈了。我怀疑,这里用了已经析构掉的 tmp 对象的地址来传值,相当于在 main 函数中使用了 operator++ 的栈空间,请问我这个猜想对吗?

写回答

1回答

quickzhao

2023-12-26

后置++应该返回对象的副本,这是C++标准规范的语义和预期行为。否则类似这样的可能行为可能会导致混淆和错误,特别是在更复杂的代码中。如 Complex c; Complex& ref = c++; 这样的语义就无法满足预期。C++的很多行为是有规范的,这源自语言本身的自由度太高,很多人会本着唯物主义的论点尝试逾越雷池而导难以预期的错误结果;有规范的自由才能带来真正的高效和正确。

0
0

重学C++ ,重构你的C++知识体系

一部大片,一段历史,构建C++知识框架的同时重塑你的编程思维

3920 学习 · 1106 问题

查看课程