目錄
第一部分 語法篇
第三章 內(nèi)存管理
在VC中,??臻g未初始化的字符默認是-52,補碼是0xCC。0xCCCC在GBK編碼中就是"燙"。
堆空間未初始化的字符默認是-51,兩個-51在GBK編碼中就是"屯"。
建議27:區(qū)分內(nèi)存分配的方式
程序由4部分組成:
- 代碼區(qū),存放程序的執(zhí)行代碼。無法控制。
- 數(shù)據(jù)區(qū),存放全局數(shù)據(jù)、常量、靜態(tài)變量等。自由存儲區(qū)、全局/靜態(tài)存儲區(qū)、常量存儲區(qū)。
- 堆區(qū),存放動態(tài)內(nèi)存
- 棧區(qū),存放程序中用到的局部數(shù)據(jù)。
內(nèi)存的5個區(qū): - 棧區(qū),存儲函數(shù)內(nèi)部變量,函數(shù)執(zhí)行完自動釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集,效率很高,缺點是分配的內(nèi)存容量有限。
- 堆區(qū),由new運算符分配的內(nèi)存塊,由
delete釋放。如果沒釋放,程序結束后操作系統(tǒng)自動回收。 - 自由存儲區(qū),由
malloc等分配的內(nèi)存塊,用free釋放。 - 全局/靜態(tài)存儲區(qū),
C的全局常量分為初始化和未初始化,C++里沒區(qū)分。 - 常量存儲區(qū),存放常量,不允許修改。
堆與棧的區(qū)別:
- 管理方式:棧由編譯器自動管理;對由開發(fā)人員自己管理。
- 空間大小:堆內(nèi)存可以占據(jù)整個內(nèi)存空間;棧內(nèi)存一般只有一定大小的空間。
- 碎片問題:頻繁地
new/delete會造成內(nèi)存空間不連續(xù);棧的數(shù)據(jù)是連續(xù)的。 - 生長方向:堆是向上增長;棧是向下增長。
- 分配方式:堆都是動態(tài)分配的。棧可能是靜態(tài)或者動態(tài)分配的。棧的靜態(tài)分配由編譯器完成,動態(tài)分配由
alloca函數(shù)完成,由編譯器釋放。 - 分配效率:堆內(nèi)存的分配效率很低,還可能引發(fā)用戶態(tài)和和心態(tài)的切換。棧內(nèi)存的分配效率很高。
因此要在合適的地方采用合適的內(nèi)存分配方式。
建議28:new/delete與new[]/delete[]必須配對使用
內(nèi)置類型沒有構造、析構函數(shù),所以使用delete或delete[]一樣。
建議29:區(qū)分new的三種形態(tài)
-
new operator,最常見的new運算符。
語言內(nèi)建的,不能重載,也不能改變其行為。做如下三件事:
(1)分配內(nèi)存(2)調(diào)用構造函數(shù)(3)返回對象的指針 -
operator new,申請原始內(nèi)存。也就是new operator的第一步。與C庫中的malloc函數(shù)很像。 -
placement new,選擇調(diào)用哪個構造函數(shù)。也就是new operator的第二步。
三種形態(tài)的用法:
- 如果只是想在堆上建立對象,使用new operator。
- 如果只是想分配內(nèi)存,使用operator new。
- 如果想在已經(jīng)獲得的內(nèi)存里建立一個對象,使用placement new。
operator new與placement new的示例:
class A{
public:
A();
A(int a);
~A();
void* operator new(size_t size);
void* operator new[](size_t size);
void operator delete(void*ptr, size_t sz);
void operator delete[](void*ptr, size_t sz);
public:
int a;
};
A::A():a(0)
{
cout << "construct A" << endl;
}
A::A(int a):a(a)
{
cout << "construct A" << endl;
}
A::~A() {
cout << "destruct A" << endl;
}
void * A::operator new(size_t sz)
{
cout << "custom operator new for size " << sz << endl;
return ::operator new(sz);
}
void * A::operator new[](size_t sz)
{
cout << "custom operator new for size " << sz << endl;
return ::operator new(sz);
}
void A::operator delete(void* ptr, size_t sz)
{
std::cout << "custom operator delete for size " << sz << endl;
::operator delete(ptr);
}
void A::operator delete[](void* ptr, size_t sz)
{
std::cout << "custom operator delete for size " << sz << endl;
::operator delete(ptr);
}
int main(int argv, char* args[]) {
cout << "1. operate new" << endl;
A *ptrA = new A(123); // operator new for size 4
cout << "(*ptrA).a: " << (*ptrA).a << endl;
cout << "sizeof ptrA: " << sizeof(ptrA) << endl; // sizeof ptrA: 8
delete ptrA; // custom operator delete for size 4
cout << endl;
size_t len = 3;
A *ptrB = new A[len]; // custom operator new for size 20
cout << "sizeof ptrB: " << sizeof(ptrB) << endl; // sizeof ptrB: 8
cout << "sizeof A: " << sizeof(A) << endl; // sizeof A: 4
for (size_t ik = 0; ik < len; ++ik) {
cout << "(*(ptrB+" << ik << ")).a: " << (*(ptrB + ik)).a << endl;
}
delete[] ptrB; // custom operator delete for size 20, (20=8+4*3)
cout << "\n2. placement new" << endl;
void* s = operator new (sizeof(A)); // 分配內(nèi)存
A* ptrC = (A*) s;
::new(ptrC) A(2019); // 調(diào)用一個參數(shù)的構造函數(shù),ptrC->A::A(2019);
cout << "(*ptrC).a: " << (*ptrC).a << endl;
::new(ptrC) A(); // 調(diào)用沒有參數(shù)的構造函數(shù),ptrC->A::A();
cout << "(*ptrC).a: " << (*ptrC).a << endl;
delete ptrC;
return 0;
}
/* Output:
1. operate new
custom operator new for size 4
construct A
(*ptrA).a: 123
sizeof ptrA: 8
destruct A
custom operator delete for size 4
custom operator new for size 20
construct A
construct A
construct A
sizeof ptrB: 8
sizeof A: 4
(*(ptrB+0)).a: 0
(*(ptrB+1)).a: 0
(*(ptrB+2)).a: 0
destruct A
destruct A
destruct A
custom operator delete for size 20
2. placement new
construct A
(*ptrC).a: 2019
construct A
(*ptrC).a: 0
destruct A
custom operator delete for size 4
*/
建議30:new內(nèi)存失敗后的正確處理方式
malloc函數(shù)在申請內(nèi)存失敗后會返回NULL。
new在申請內(nèi)存失敗后會拋一個異常,不會執(zhí)行if(ptr == NULL){}
// new失敗拋異常
char *pStr = new string[SIZE];
if(pStr == NULL){
// 如果new失敗,不會執(zhí)行
return false;
}
// new失敗不拋異常
char *pStr = new(std::nothrow) string[SIZE];
if(pStr == NULL){
// 如果new失敗,不會執(zhí)行
return false;
}
如果想檢查new是否成功,則應該捕捉異常:
try{
char *pStr = new string[SIZE];
}catch(const bad_alloc& e){
return -1;
}
一般來說,new失敗可能是內(nèi)存不足引起的,捕獲這個異常沒有意義,可以直接core dump。
建議31:new內(nèi)存失敗后會調(diào)用new_handler函數(shù)
建議32:檢測內(nèi)存泄露工具
原理:截獲對內(nèi)存分配和內(nèi)存釋放的函數(shù)的調(diào)用。每當分配一塊內(nèi)存時,將其加入一個全局內(nèi)存鏈中,每當釋放一塊內(nèi)存時,將其刪除。程序結束后,內(nèi)存鏈中剩余的指針就是未被釋放的。
(1)Windows平臺:MS C-Runtime Libraay、BoundsChecker、Insure++
(2)Linux平臺:Rational Purify、Valgrind
建議33:operator new/operator delete的重載
- 重載的
operator new必須是類成員函數(shù)或全局函數(shù),不可以是某一個命名空間之內(nèi)的函數(shù)或全局靜態(tài)函數(shù)。 - 因為
operator new是在類的具體對象被構建出來之前調(diào)用的,在調(diào)用operator new的時候this指針尚未誕生,因此重載的operator new必須是static的。同理,operator delete也是static的。 -
operator new與operator delete成對重載 -
delete會調(diào)用free函數(shù)
建議34:智能指針
RAII(Resource Acquisition In Initialization)
智能指針實際是一個指針類,將指針通過構造函數(shù)傳遞給指針類的對象,當這個對象析構時,將指針delete。
不能使用臨時的share_ptr對象。當將share_ptr對象作為函數(shù)參數(shù)時,可能會由于函數(shù)參數(shù)求值順序,引起內(nèi)存泄露。
建議35:內(nèi)存池
當程序中需要頻繁地申請大小相同的內(nèi)存,用內(nèi)存池技術能提高效率。
內(nèi)存池技術通過批量申請內(nèi)存,降低內(nèi)存申請次數(shù),節(jié)省時間,且能減少內(nèi)存碎片的產(chǎn)生。