網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
??今天遇到的是內(nèi)存釋放錯(cuò)誤的問(wèn)題。原因是沒(méi)寫(xiě)拷貝構(gòu)造函數(shù),奇怪的是我之前也沒(méi)寫(xiě)確實(shí)能正常工作的,今天深究了一下發(fā)現(xiàn)是編譯器做了返回值優(yōu)化。
問(wèn)題背景
??編譯環(huán)境還是針對(duì)C6455 DSP,為了做一些簡(jiǎn)單的圖像直方圖的處理,并且盡可能不用模板類,我自己寫(xiě)了一個(gè)簡(jiǎn)單的類用來(lái)存放帶長(zhǎng)度信息的數(shù)組,并且可以做一些簡(jiǎn)單的運(yùn)算。重載了減法運(yùn)算符,從而可以對(duì)兩個(gè)直方圖求差。
具體問(wèn)題
??當(dāng)類中有那種需要?jiǎng)討B(tài)分配空間的成員的時(shí)候,要記得提醒自己重載拷貝構(gòu)造函數(shù)和賦值運(yùn)算符,這兩者缺一不可。為了舉個(gè)例子,我定義了下面這個(gè)CData類。
class CData
{
private:
int *m_pData;
unsigned int m_nLen;
public:
CData();
CData(unsigned int nLen);
/* There is no copy constructor at the very begining */
CData(const CData &data);
~CData();
int &at(unsigned int i);
int &at(unsigned int i) const;
CData operator-(const CData &data);
CData &operator=(const CData &data);
CData &operator-=(const CData &data);
};
??在CData中,有兩個(gè)成員,分別是一個(gè)int類型的指針和一個(gè)unsigned int類型的變量代表數(shù)據(jù)的個(gè)數(shù)。
??我重載了一個(gè)減法運(yùn)算符,為的是將兩個(gè)CData類型的數(shù)據(jù)長(zhǎng)度相同的對(duì)象中的每個(gè)對(duì)應(yīng)位置的int類型數(shù)據(jù)直接相減。而在調(diào)用減法運(yùn)算的時(shí)候勢(shì)必會(huì)用到拷貝構(gòu)造函數(shù)和賦值運(yùn)算符,因此也需要對(duì)它們重載。
??順帶一提的是,在上面我還聲明了一個(gè)“-=”運(yùn)算符的重載,為的是突出“-=”在一定場(chǎng)合會(huì)更加高效,但因?yàn)槲以趯?shí)際應(yīng)用中不能直接覆蓋“被減數(shù)”中的數(shù)據(jù),所以不能用“-=”運(yùn)算符,只能通過(guò)減法運(yùn)算符來(lái)實(shí)現(xiàn)。
CData CData::operator-(const CData &data)
{
cout << "entering subtract" << endl;
assert(m_nLen == data.m_nLen);
CData ret(data.m_nLen);
for (int i = 0; i < m_nLen; i++){
ret.at(i) = at(i) - data.at(i);
}
return ret;
}
??減法運(yùn)算重載,返回值是一個(gè)類的對(duì)象。函數(shù)返回后,這個(gè)類的對(duì)象就已經(jīng)被析構(gòu)了,所以在它被析構(gòu)之前一般會(huì)先調(diào)用一個(gè)拷貝構(gòu)造函數(shù),將這個(gè)類的對(duì)象拷貝給一個(gè)新的臨時(shí)的對(duì)象。默認(rèn)拷貝構(gòu)造函數(shù)采用淺拷貝,這在遇到類中包含指針類型的成員時(shí)很可能造成對(duì)同一塊內(nèi)存區(qū)域的重復(fù)釋放。所以我們需要重載拷貝構(gòu)造函數(shù),為指針成員申請(qǐng)新的內(nèi)存空間。
CData CData::&operator=(const CData &data)
{
cout << "entering assign" << endl;
assert(m_nLen == data.m_nLen);
for (int i = 0; i < m_nLen; i++)
at(i) = data.at(i);
return *this;
}
??減法運(yùn)算返回的臨時(shí)對(duì)象如果不給新的對(duì)象接收,它就沒(méi)了。所以我們還需要重載賦值運(yùn)算符,用來(lái)接收這個(gè)臨時(shí)對(duì)象中的數(shù)據(jù),這一臨時(shí)對(duì)象被接收后,也會(huì)被析構(gòu)。分析整個(gè)過(guò)程可以看到,從減法運(yùn)算返回的類對(duì)象,其中的數(shù)據(jù)先要被復(fù)制到一個(gè)臨時(shí)對(duì)象里,再要從臨時(shí)對(duì)象復(fù)制到真正的賦值目標(biāo)對(duì)象里,浪費(fèi)了很多時(shí)間。
??有的編譯器就會(huì)對(duì)這樣的情況進(jìn)行優(yōu)化,省略對(duì)拷貝構(gòu)造函數(shù)的調(diào)用,直接賦值。
??我今天遇到的問(wèn)題是因?yàn)椋抑安恢涝谶@個(gè)過(guò)程中會(huì)調(diào)用拷貝構(gòu)造函數(shù),所以沒(méi)有重寫(xiě)拷貝構(gòu)造函數(shù)。意外的是,之前我這么做也是能用的,但我現(xiàn)在覺(jué)得不管怎樣還是得把拷貝構(gòu)造函數(shù)寫(xiě)上,不能偷懶。
測(cè)試代碼
??為了便于驗(yàn)證這一過(guò)程,我寫(xiě)了一部分測(cè)試代碼,可以用g++編譯。
#include <assert.h>
#include <iostream>
using namespace std;
class CData
{
private:
int *m_pData;
unsigned int m_nLen;
public:
CData(): m_nLen(0), m_pData(nullptr)
{
cout << "construct without arg" << endl;
}
CData(unsigned int nLen): m_nLen(nLen)
{
cout << "construct with arg" << endl;
m_pData = new int[nLen];
assert(m_pData != NULL);
}
CData(const CData &data)
{
cout << "copy construct" << endl;
m_nLen = data.m_nLen;
m_pData = new int[data.m_nLen];
assert(m_pData != nullptr);
for (int i = 0; i < m_nLen; i++){
at(i) = data.at(i);
}
}
~CData()
{
cout << "destruct" << endl;
if(m_pData){
delete[] m_pData;
m_pData = nullptr;
}
}
int &at(unsigned int i)
{
assert(i < m_nLen);
return *(m_pData + i);
}
int &at(unsigned int i) const
{
assert(i < m_nLen);
return *(m_pData + i);
}
CData operator-(const CData &data)
{
cout << "entering subtract" << endl;
assert(m_nLen == data.m_nLen);
CData ret(data.m_nLen);
for (int i = 0; i < m_nLen; i++){
ret.at(i) = at(i) - data.at(i);
}
return ret;
}
CData &operator=(const CData &data)
{
cout << "entering assign" << endl;
assert(m_nLen == data.m_nLen);
for (int i = 0; i < m_nLen; i++)
at(i) = data.at(i);
return *this;
}
CData &operator-=(const CData &data)
{
cout << "entering subtract and assign" << endl;
assert(m_nLen == data.m_nLen);
for (int i = 0; i < m_nLen; i++)
at(i) -= data.at(i);
return *this;
}
};
int main()
{
cout << "---------- start construct ----------" << endl;
CData dataA(10);
CData dataB(10);
CData dataC(10);
cout << "---------- start subtract ----------" << endl;
dataC = dataA - dataB;
cout << "---------- start subtract and assign ----------" << endl;
dataA -= dataB;
cout << "---------- end of program ----------" << endl;
return 0;
}
??g++編譯的程序,返回類對(duì)象的時(shí)候,默認(rèn)是會(huì)省略拷貝構(gòu)造函數(shù)的,加上“-fno-elide-constructors”的編譯選項(xiàng)后可以讓它不省略。
??上圖是在加上了“-fno-elide-constructors”編譯選項(xiàng)后的運(yùn)行結(jié)果,可以看到執(zhí)行了兩次析構(gòu),對(duì)應(yīng)減法重載函數(shù)內(nèi)的對(duì)象和拷貝構(gòu)造函數(shù)生成的臨時(shí)對(duì)象被析構(gòu)。
??而沒(méi)有“-fno-elide-constructors”選項(xiàng)的時(shí)候,就只會(huì)有一次析構(gòu),因?yàn)闆](méi)有調(diào)用拷貝構(gòu)造函數(shù)。
??如果加了這個(gè)選項(xiàng),但是沒(méi)有重寫(xiě)拷貝構(gòu)造函數(shù),就會(huì)在第二次析構(gòu)的時(shí)候產(chǎn)生重復(fù)釋放內(nèi)存的問(wèn)題。
原文鏈接:https://blog.csdn.net/qq_35787848/article/details/124355291
相關(guān)推薦
- 2022-08-27 C#使用百度Ueditor富文本框?qū)崿F(xiàn)上傳文件_C#教程
- 2022-12-16 Python海象運(yùn)算符代碼分析及知識(shí)點(diǎn)總結(jié)_python
- 2023-11-21 功率和能量換算公式、如何換算,W和J如何轉(zhuǎn)換,power和energy轉(zhuǎn)換
- 2022-06-12 python將Dataframe格式的數(shù)據(jù)寫(xiě)入opengauss數(shù)據(jù)庫(kù)并查詢_python
- 2022-08-21 Python?Map函數(shù)保姆級(jí)使用教程_python
- 2022-04-01 k8s記一次kubelet啟動(dòng)后master無(wú)法獲取node信息
- 2022-08-11 python實(shí)現(xiàn)對(duì)excel中需要的數(shù)據(jù)的單元格填充顏色_python
- 2022-09-16 Python中的?No?Module?named?***問(wèn)題及解決_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支