关于后置++操作符中返回 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++的很多行为是有规范的,这源自语言本身的自由度太高,很多人会本着唯物主义的论点尝试逾越雷池而导难以预期的错误结果;有规范的自由才能带来真正的高效和正确。
00
相似问题