《C++高階程式設計》之--有效的記憶體管理
阿新 • • 發佈:2019-01-31
第三部分、有效的記憶體管理
一、使用動態記憶體
在程式中使用動態記憶體有兩個主要的優點:
·動態記憶體可以在不同的物件與函式之間共享。
·動態分配的記憶體空間的大小可以在執行時確定。
1、 如何描述記憶體
控制代碼:用於描述一個指標,它指向的也是一個指標,後者指向某個記憶體單元。
2、 記憶體的分配與撤銷
new int; à 孤立記憶體
new不僅僅會分配記憶體,它還會構造物件。
int* ptr = new (nothrow) int; //new的另外一個版本,它不會丟擲異常,而只是返回一個NULL。
3、 陣列
·把陣列放在堆中的優點:可以使用動態記憶體在執行時定義陣列的長度 。
·注:儘量不要再C++中使用realloc()函式,因為使用者定義的物件不能很好的進行按位複製。
·物件陣列:使用new會對每個物件自動呼叫無引數的建構函式,這樣一來,使用new分配物件陣列就會返回一個指標,它指向一個充分構建並已初始化的物件陣列。
·刪除陣列:delete [] object_ptr;
·不要把new和delete與new[]和delete[]相混淆,它們是分別配對使用的。
4、多維堆陣列
基於堆的多維陣列不像基於棧的多維陣列那樣工作,為其分配的記憶體不是連續的。正確的做法:—〉必須先為基於堆的陣列的第一維下標分配一個連續的陣列,該陣列的每個元素實際上是指向另一個數組指標的,這個陣列儲存了對應第二維下標的元素。
例:new:
char** array = new char*[xValue]; //Allocate first dimension.
for(int i = 0; i < xValue; i++) {
array[i] = new char[yValue]; //Allocate ith dimension.
}
delete:
for(int i = 0; i < xValue; i++) {
delete [] arrar[i]; //delete ith..
}
delete [] array; // delete first..
5、使用指標
多層指標實際上只是訪問資料過程中的各個步驟。
對指標解除引用,是告訴程式要在記憶體再深入一層。
1、指標型別強制轉換
既然指標僅僅是記憶體地址,所以指標時弱型別的。指向XML文件的指標與指向整數的指標大小也是一樣的。通過使用C風格的型別強制轉換,編譯器可以很容易地把任何指標型別強制轉換為另一個指標型別。
例:Document* documentPtr = GetDocument();
char* charPtr = (char*)documentPtr;
static型別強制轉換的安全性更高些。編譯器會拒絕對指向不同資料型別的指標完成static型別強制轉換。
例:static_cast<char*> (documentPtr);//BUG...
如果要強制轉換型別的兩個指標實際上是指向通過繼承相關聯的物件,編譯器會允許完成static型別強制轉換,但是在繼承層次結構中,動態的(dynamic_cast)型別強制轉換更為安全。
2、const指標
如果const位於型別前面,它表示指標指向的指是受保護的,在陣列中,就表示陣列的各個元素是const的。
要保護指標本身,關鍵字const要直接位於變數名的前面。
例:const int* const value; //指標和指標指向的值都是受保護的。
二、陣列與指標的對應
在堆上分配的陣列由指向第一個元素的指標來引用。
1、陣列即指標
使用陣列語法宣告的陣列可以通過指標來訪問,把陣列傳遞給函式時,總是作為指標傳遞的(效率考慮的可能性)。
例:void doubleInts(int* arrary, int size); //指標傳遞
void doubleInts(int array[], int size); //也是指標傳遞
2、指標並非都是陣列
指標和陣列之間存在微妙但很重要的區別,指標和陣列有很多共同的性質,有時候可以交替使用,但是它們並不完全相同。
例:int* ptr = new int; //陣列會自動引用為指標,但是並非所有的指標都是陣列。
三、動態字串
1、C風格的字串
C語言中,字串表示為字元陣列。
2、字串直接量
例:cout << “hello” << endl; //字串直接量或字串字面量
與字串直接量相關聯的具體記憶體空間位於記憶體的只讀部分,這就是為什麼他是常量字元陣列的原因。這就允許編譯器通過重用指向等價字串直接量的引用來優化記憶體的使用(即使程式使用了字串直接量“hello”500次,編譯器只是在記憶體中建立hello一個例項)。
3、C++的字串類
string類作為C++標準庫的一部分,實際上是basic_string模板類的一個例項化,這個類最好的一點,如果使用得當,string類會負責分配記憶體。
由於相容性考慮,可以使用方法c_str()把C++string轉化為C風格的字串。
四、低階的記憶體操作
1、指標運算
C++編譯器使用指標的宣告型別來支援完成指標運算。
指標運算的另一個有用的應用涉及到減法:將一個指標減去同類型的另一個指標,所得到的是兩個指標之間的元素個數,而不是兩個指標之間的絕對位元組數。
2、自定義記憶體管理
基本上內建的記憶體分配功能就已經足夠了,在後臺new和delete會完成所有的這些工作:以適當大小的塊分配記憶體,維護可用的記憶體列表,刪除記憶體時再把記憶體塊釋放到可用的記憶體列表中。
使用new分配記憶體時,程式還需要保留一小塊空間來記錄已經分配了多少記憶體空間,這樣,呼叫delete時就可以釋放適當數量的記憶體。對於大部分物件,相對於分配的記憶體來說,這個開銷小的多,所以並沒有太大的區別。然而,對於小物件或大量物件的程式,這種開銷可能會有較大的影響。
3、垃圾回收
垃圾回收存在以下缺點:
·垃圾回收器主動執行時,可能會使程式的執行減慢。
·如果程式大量地分配記憶體,那麼垃圾回收器可能跟不上這個速度。
·如果垃圾回收器本身有bug,或者認為一個已經拋棄的物件仍然在使用,可能會造成 不可恢復的記憶體洩漏。
4、物件池
物件池模擬了記憶體的再利用,來提高效能效率的細節問題。
5、函式指標
正常情況下,不會考慮函式在記憶體中的位置,但是每個函式的確都位於一個特定的地址上。在C++中,可以把函式作為資料使用,換句話說,可以把函式的地址作為引數,就像變數一樣使用。
函式指標根據引數型別和相容函式的返回型別來確定型別。
例:typedef bool(*YesNoFunc)(int, int);
參考程式碼:
/*
* NOTE: func_pointer.cpp -- function pointer test..
*
*/
#include <iostream>
using namespace std;
typedef bool(*func_p)(int, int);
bool is_equal(int l_par, int r_par);
bool is_no_equal(int l_par, int r_par);
void find_match(int values1[], int values2[], int size, func_p is_function);
int main(int argc, char* argv[])
{
int arr_1[5] ={ 2, 4, 7, 2, 5 };
int arr_2[5] ={ 3, 4, 0, 2, 5 };
find_match(arr_1, arr_2, 5, is_equal);
//find_match(arr_1, arr_2, 5, &is_equal);
find_match(arr_1, arr_2, 5, is_no_equal);
return0;
}
void find_match(int values1[], int values2[], int size, func_p is_function)
{
for (int i =0; i < size; i++) {
if (is_function(values1[i], values2[i])) {
cout <<"match found at position "<< i <<
" ("<< values1[i] <<", "<< values2[i] <<") "<< endl;
}
}
}
bool is_equal(int l_par, int r_par)
{
return (l_par == r_par);
}
bool is_no_equal(int l_par, int r_par)
{
return (!(l_par == r_par));
}
五、常見的記憶體缺陷 1、字串空間分配不足 C風格的字串操作函式有可能把字串的最後部分寫到固定大小之外的空間中。 2、記憶體洩漏 Valgrind免費工具是面向Linux的開源工具,用於記憶體跟蹤。 3、智慧指標(smart pointer) 智慧指標概念源於這樣一個事實:即如果把一切都放在棧中,就可以避免與記憶體相關的大多數問題。棧比堆更安全,因為棧變數超出作用域時,它們會自動撤銷和清除。智慧指標結合了棧變數的安全性和對變數的靈活性。 智慧指標基本原理:它是一個帶有關聯指標的物件,當智慧指標超出作用域時,會刪除關聯的指標,本質上講,就是在一個基於棧的物件內部包裝一個堆物件。 C++標準模板庫提供了一個智慧指標的實現:auto_ptr。可以把動態分配的物件儲存在基於棧的auto_ptr例項中,而不是儲存在指標中。不需要顯示地釋放與auto_ptr關聯的記憶體,auto_ptr超出作用域時,與之關聯的記憶體會得到清除。 例:auto_ptr<Simple> mySimple(new Simple()); 智慧指標也可以象標準指標一樣,解除引用,呼叫函式等。 4、二次刪除與無效指標 Valgrind會檢查二次刪除和使用已釋放的物件的問題。 5、訪問越界指標 導致越界(超過陣列)寫記憶體的bug經常稱為[緩衝區溢位錯誤]。
* NOTE: func_pointer.cpp -- function pointer test..
*
*/
#include <iostream>
using namespace std;
typedef bool(*func_p)(int, int);
bool is_equal(int l_par, int r_par);
bool is_no_equal(int l_par, int r_par);
void find_match(int values1[], int values2[], int size, func_p is_function);
int main(int argc, char* argv[])
{
int arr_1[5] ={ 2, 4, 7, 2, 5 };
int arr_2[5] ={ 3, 4, 0, 2, 5 };
find_match(arr_1, arr_2, 5, is_equal);
//find_match(arr_1, arr_2, 5, &is_equal);
find_match(arr_1, arr_2, 5, is_no_equal);
return0;
}
void find_match(int values1[], int values2[], int size, func_p is_function)
{
for (int i =0; i < size; i++) {
if (is_function(values1[i], values2[i])) {
cout <<"match found at position "<< i <<
" ("<< values1[i] <<", "<< values2[i] <<") "<< endl;
}
}
}
bool is_equal(int l_par, int r_par)
{
return (l_par == r_par);
}
bool is_no_equal(int l_par, int r_par)
{
return (!(l_par == r_par));
}
五、常見的記憶體缺陷 1、字串空間分配不足 C風格的字串操作函式有可能把字串的最後部分寫到固定大小之外的空間中。 2、記憶體洩漏 Valgrind免費工具是面向Linux的開源工具,用於記憶體跟蹤。 3、智慧指標(smart pointer) 智慧指標概念源於這樣一個事實:即如果把一切都放在棧中,就可以避免與記憶體相關的大多數問題。棧比堆更安全,因為棧變數超出作用域時,它們會自動撤銷和清除。智慧指標結合了棧變數的安全性和對變數的靈活性。 智慧指標基本原理:它是一個帶有關聯指標的物件,當智慧指標超出作用域時,會刪除關聯的指標,本質上講,就是在一個基於棧的物件內部包裝一個堆物件。 C++標準模板庫提供了一個智慧指標的實現:auto_ptr。可以把動態分配的物件儲存在基於棧的auto_ptr例項中,而不是儲存在指標中。不需要顯示地釋放與auto_ptr關聯的記憶體,auto_ptr超出作用域時,與之關聯的記憶體會得到清除。 例:auto_ptr<Simple> mySimple(new Simple()); 智慧指標也可以象標準指標一樣,解除引用,呼叫函式等。 4、二次刪除與無效指標 Valgrind會檢查二次刪除和使用已釋放的物件的問題。 5、訪問越界指標 導致越界(超過陣列)寫記憶體的bug經常稱為[緩衝區溢位錯誤]。