C和C++安全编码笔记:动态内存管理
4.1 C內存管理:
C標準內存管理函數:
(1).malloc(size_t size):分配size個字節,并返回一個指向分配的內存的指針。分配的內存未被初始化為一個已知值。
(2).aligned_alloc(size_t alignment, size_t size):為一個對象分配size個字節的空間,此對象的對齊方式是alignment指定的。alignment的值必須是實現支持的一種有效的對齊方式,size的值必須是alignment的整數倍,否則行為就是未定義的。此函數返回一個指向分配的空間的指針,如果分配失敗,則返回一個空指針。
(3).realloc(void* p, size_t size):將p所指向的內存塊的大小改為size個字節。新大小和舊大小中較小的值那部分內存所包含的內容不變,新分配的內存未作初始化,因此將有不確定的值。如果內存請求不能被成功分配,那么舊的對象保持不變,而且沒有值被改變。如果p是一個空指針,則該調用等價于malloc(size);如果size等于0,則該調用等價于free(p),但這種釋放內存的用法應該避免。
(4).calloc(size_t nmemb, size_t size):為數組分配內存,(該數組共有nmemb個元素,每個元素的大小為size個字節)并返回一個指向所分配的內存的指針。所分配的內存的內容全部被設置為0。
內存分配函數返回一個指向分配的內存的指針,這塊內存是按照任何對象類型恰當地對齊的,如果請求失敗,則返回一個空指針。連續調用內存分配函數分配的存儲空間的順序和鄰接是不確定的。所分配的對象的生存期從分配開始,到釋放時結束。返回的指針指向所分配的空間的起始地址(最低字節地址)。
free(void* p):釋放由p指向的內存空間,這個p必須是先前通過調用aligned_alloc、malloc、calloc或realloc返回的。如果引用的內存不是被這些函數之一分配的或free(p)此前已經被調用過,將會導致未定義行為。如果p是一個空指針,則不執行任何操作。
由C內存分配函數分配的對象有分配存儲期限。存儲期限是一個對象的屬性,它定義了包含該對象存儲的最低潛在生存期。這些對象的生存期并不限于創建它的范圍內,因此,如果在一個函數內調用malloc,那么在該函數返回后,已分配的內存仍然存在。
對齊:完整的對象類型有對齊(alignment)要求,這種要求對可以分配該類型對象的地址施加限制。對齊是實現定義的整數值,它表示可以在一個連續的地址之間分配給指定的對象的字節數量。對象類型規定了每一個該類型對象的對齊要求。消除對齊要求,往往需要生成代碼進行跨字邊界域訪問,或從更慢的奇數地址訪問,從而減慢內存訪問。
完整的對象(complete object):對象中可以包含其它對象,被包含的對象稱為子對象(subobject)。子對象可以是成員子對象、基類子對象,或數組元素。如果一個對象不是任何其它對象的子對象,那么它被稱為一個完整的對象。
對齊有一個從較弱的對齊到較強的對象(或更嚴格的對齊)的順序。越嚴格的對齊,其對齊值越大。一個地址滿足某一種對齊要求,也滿足任何更弱的有效對齊要求。char、signed char、unsigned char類型的對齊要求最弱。對齊表示為size_t類型的值。每個有效對齊值都是2的一個非負整數冪。有效的對齊包括基本類型的對齊,加上額外的一組可選的實現定義的值。
基本對齊(fundamental alignment)小于或等于在所有上下文中由編譯器支持的最大對齊。max_align_t類型的對齊與在所有上下文中由編譯器支持的對齊大小相同。擴展對齊(extended alignment)大于max_align_t類型的對齊。一個具有擴展對齊要求的類型也稱為超對齊(overaligned)類型。每個超對齊類型,要么是一個結構或聯合類型,其中一個成員已應用擴展對齊,要么它包含這樣的類型。如果實現支持,可以使用aligned_alloc函數分配比正常更嚴格的對齊的內存。如果一個程序要求比alignof(max_align_t)更大的對齊,那么這個程序是不可移植的,因為對超對齊類型的支持是可選的。
void test_aligned_alloc()
{const int arr_size = 11;// 分配16字節對齊的數據
#ifdef _MSC_VERfloat* array = (float*)_aligned_malloc(16, arr_size * sizeof(float));
#elsefloat* array = (float*)aligned_alloc(16, arr_size * sizeof(float));
#endifauto addr = std::addressof(array);fprintf(stdout, "pointer addr: %p\n", addr);fprintf(stdout, "char alignment: %d, float alignment: %d, max_align_t alignment: %d\n",alignof(char), alignof(float), alignof(max_align_t));
} 
在C標準中引入_Alignas關鍵字和aligned_alloc函數的主要理由是支持單指令多數據(SIMD)計算。
4.2 常見的C內存管理錯誤:常見的與內存管理相關的編程缺陷包括:初始化錯誤、未檢查返回值、對空指針或無效指針解引用、引用已釋放的內存、對同一塊內存釋放多次、內存泄漏和零長度分配。
初始化錯誤:由malloc函數返回的空間中的值是不確定的。一個常見的錯誤是不正確地假設malloc把分配的內存的所有位都初始化為零。
// 讀取未初始化的內存
void test_memory_init_error()
{// 初始化大的內存塊可能會降低性能并且不總是必要的.// C標準委員會決定不需要malloc來初始化這個內存,而把這個決定留給程序員int n = 5;int* y = static_cast<int*>(malloc(n * sizeof(int)));int A[] = {1, 2, 3, 4, 5};for (int i = 0; i < n; ++i) {y[i] += A[i];}std::for_each(y, y+n, [](int v) { fprintf(stdout, "value: %d\n", v); });free(y);
} 
不要假定內存分配函數初始化內存。不要引用未初始化的內存。
清除或覆寫內存通常是通過調用C標準的memset函數來完成的。遺憾的是,如果不在寫后訪問內存,編譯器優化可能會默默地刪除對memset函數的調用。
未檢查返回值:內存分配函數的返回值表示分配失敗或成功。如果請求的內存分配失敗,那么aligned_alloc、calloc、malloc和realloc函數返回空指針。
// 檢查malloc的返回值
int* test_memory_return_value()
{// 如果不能分配請求的空間,那么C內存分配函數將返回一個空指針int n = 5;int* ptr = static_cast<int*>(malloc(sizeof(int) * n));if (ptr != nullptr) {memset(ptr, 0, sizeof(int) * n);} else {fprintf(stderr, "fail to malloc\n");return nullptr;}return ptr;
} 
Null或無效指針解引用:用一元操作符”*”解引用的指針的無效值包括:空指針、未按照指向的對象類型正確對齊的地址、生存期結束后的對象的地址。
空指針的解引用通常會導致段錯誤,但并非總是如此。許多嵌入式系統有映射到地址0處的寄存器,因此覆寫它們會產生不可預知的后果。在某些情況下,解引用空指針會導致任意代碼的執行。
引用已釋放內存:除非指向某塊內存的指針已設置為NULL或以其它方式被覆寫,否則就有可能訪問已被釋放的內存。
// 引用已釋放內存
void test_memory_reference_free()
{int* x = static_cast<int*>(malloc(sizeof(int)));*x = 100;free(x);// 從已被釋放的內存讀取是未定義的行為fprintf(stderr, "x: %d\n", *x);// 寫入已經被釋放的內存位置,也不大可能導致內存故障,但可能會導致一些嚴重的問題*x = -100;fprintf(stderr, "x: %d\n", *x);
} 
從已被釋放的內存讀取是未定義的行為,但在沒有內存故障時幾乎總能成功,因為釋放的內存是被內存管理器回收的。然而,并不保證內存的內容沒有被篡改過。雖然free函數調用通常不會擦除內存,但內存管理器可能使用這個空間的一部分來管理釋放或未分配的內存。如果內存塊已被重新分配,那么其內容可能已經被全部替換。其結果是,這些錯誤可能檢測不出來,因為內存中的內容可能會在測試過程中被保留,但在運行過程中被修改。
寫入已經被釋放的內存位置,也不太可能導致內存故障,但可能會導致一些嚴重的問題。如果該塊內存已被重新分配,程序員就可以覆寫此內存,一個內存塊是專門(dedicated)為一個特定的變量分配的,但在現實中,它是被共享(shared)的。在這種情況下,該變量中包含最后一次寫入的任何數據。如果那塊內存沒有被重新分配,那么寫入已釋放的塊可能會覆寫并損壞內存管理器所使用的數據結構。
多次釋放內存:最常見的場景是兩次釋放(double-free)。這個錯誤是危險的,因為它會以一種不會立即顯現的方式破壞內存管理器中的數據結構。
// 多次釋放內存
void test_memory_multi_free()
{int* x = static_cast<int*>(malloc(sizeof(int)));free(x);// 多次釋放相同的內存會導致可以利用的漏洞free(x);
} 
內存泄漏:當動態分配的內存不再需要后卻沒有被釋放時,就會發生內存泄漏。
零長度分配:C標準規定:如果所要求的空間大小是零,其行為是實現定義的:要么返回一個空指針,要么除了不得使用返回的指針來訪問對象以外,行為與大小仿佛是某個非零值。
// 零長度分配:不要執行零長度分配
void test_memory_0_byte_malloc()
{char* p1 = static_cast<char*>(malloc(0));fprintf(stderr, "p1 pointer: %p\n", std::addressof(p1)); // 是不確定的free(p1);p1 = nullptr;char* p2 = static_cast<char*>(realloc(p1, 0));fprintf(stderr, "p2 pointer: %p\n", std::addressof(p2)); // 是不確定的free(p2);int nsize = 10;char* p3 = static_cast<char*>(malloc(nsize));char* p4 = nullptr;// 永遠不要分配0個字節if ((nsize == 0) || (p4 = static_cast<char*>(realloc(p3, nsize))) == nullptr) {free(p3);p3 = nullptr;return;}p3 = p4;free(p3);
} 
此外,要求分配0字節時,成功調用內存分配函數分配的存儲量是不確定的。在內存分配函數返回一個非空指針的情況下,讀取或寫入分配的內存區域將導致未定義的行為。通常情況下,指針指向一個完全由控制結構組成的零長度的內存塊。覆寫這些控制結構損害內存所使用的數據結構。
realloc函數將釋放舊對象,并返回一個指針,它指向一個具有指定大小的新對象。然而,如果不能為新對象分配內存,那么它就不釋放舊對象,而且舊對象的值是不變的。正如malloc(0),realloc(p, 0)的行為是實現定義的。
不要執行零長度分配。
4.3 C++的動態內存管理:在C++中,使用new表達式分配內存并使用delete表達式釋放內存。C++的new表達式分配足夠的內存來保存所請求類型的對象,并可以初始化所分配的內存中的對象。
new表達式是構造一個對象的唯一方法,因為不可能顯示地調用構造函數。分配的對象類型必須是一個完整的對象類型,并且不可以(例如)是一個抽象類類型或一個抽象類數組。對于非數組對象,new表達式返回一個指向所創建的對象的指針;對于數組,它返回一個指向數組初始元素的指針。new表達式分配的對象有動態存儲期限(dynamic storage duration)。存儲期限定義了該對象包含的存儲的生存期。使用動態存儲的對象的生存期,不局限于創建該對象所在的范圍。
如果提供了初始化參數(即類的構造函數的參數,或原始整數類型的合法值),那么由new操作符所分配的內存被初始化。只有一個空的new-initializer()存在時,”普通的舊數據”(POD)類型的對象是new默認初始化(清零)的。這包括所有的內置類型。
void test_memory_new_init()
{// 包括所有的內置類型int* i1 = new int(); // 已初始化int* i2 = new int; // 未初始化fprintf(stdout, "i1: %d, i2: %d\n", *i1, *i2);// 就地new沒有實際分配內存,所以該內存不應該被釋放int* i3 = new (i1) int;fprintf(stdout, "i3: %d\n", *i3);delete i1;delete i2;// 通常情況下,分配函數無法分配存儲時拋出一個異常表示失敗int* p1 = nullptr;try {p1 = new int;} catch (std::bad_alloc) {fprintf(stderr, "fail to new\n");return;}delete p1;// 用std::nothrow參數調用new,當分配失敗時,分配函數不會拋出一個異常,它將返回一個空指針int* p2 = new(std::nothrow) int;if (p2 == nullptr) {fprintf(stderr, "fail to new\n");return;}delete p2;
} 
就地new(placement new)是另一種形式的new表達式,它允許一個對象在任意內存位置構建。就地new需要在指定的位置有足夠的內存可用。因為就地new沒有實際分配內存,所以該內存不應該被釋放。
分配函數:必須是一個類的成員函數或全局函數,不能在全局范圍以外的命名空間范圍中聲明,并且不能把它在全局范圍內聲明為靜態的。分配函數的返回類型是void*。
分配函數試圖分配所請求的存儲量。如果分配成功,則它返回存儲塊的起始地址,該塊的長度(以字節為單位)至少為所要求的大小。分配函數返回的分配的存儲空間內容沒有任何限制。連續調用分配函數分配的存儲的順序、連續性、初始值都是不確定的。返回的指針是適當地對齊的,以便它可以被轉換為任何具有基本對齊要求的完整對象類型的指針,并在之后用于訪問所分配的存儲中的對象或數組(直到調用一個相應的釋放函數顯示釋放這塊存儲)。即使所請求的空間大小為零,請求也可能會失敗。如果請求成功,那么返回值是一個非空指針值。如果一個指針是大小為零的請求返回的,那么對它解引用的效果是未定義的。C++在發起一個為零的請求時行為與C不同,它返回一個非空指針。
通常情況下,分配函數無法分配存儲時拋出一個異常表示失敗,這個異常將匹配類型為std::bad_alloc的異常處理器。
如果用std::nothrow參數調用new,當分配失敗時,分配函數不會拋出一個異常。相反,它將返回一個空指針。
當一個異常被拋出時,運行時機制首先在當前范圍內搜索合適的處理器。如果當前范圍內沒有這樣的處理程序存在,那么控制權將由當前范圍轉移到調用鏈中的一個更高的塊。這個過程一直持續,直到找到一個合適的處理程序為止。如果在任何級別中都沒有處理程序捕獲該異常,那么std::terminate函數被自動調用。默認情況下,terminate調用標準C庫函數abort,它會突然退出程序。當abort被調用時,沒有正常的程序終止函數調用發生,這意味著全局和靜態對象的析構函數不執行。
在C++中,處理分配和分配失敗的標準慣用法是資源獲取初始化(Resource Acquisition Is Initializatiton, RAII)。RAII運用C++的對象生存期概念控制程序資源,如內存、文件句柄、網絡連接、審計跟蹤等。要保持對資源的跟蹤,只要創建一個對象,并把資源的生存期關聯到對象的生存期即可。這使你可以使用C++對象管理設施來管理資源。其最簡單的形式是,創建一個對象,它在構造函數獲得資源,并在其析構函數中釋放資源。
class intHandle {
public:explicit intHandle(int* anInt) : i_(anInt) {} // 獲取資源~intHandle() { delete i_; } // 釋放資源intHandle& operator=(const int i){*i_ = i;return *this;}int* get() { return i_; } // 訪問資源private:intHandle(const intHandle&) = delete;intHandle& operator=(const intHandle&) = delete;int* i_;
};// 資源獲取初始化(Resource Acquisition Is Initialization, RAII)
void test_memory_arii()
{intHandle ih(new int);ih = 5;fprintf(stdout, "value: %d\n", *ih.get());// 使用std::unique_ptr能完成同樣的事情,而且更簡單std::unique_ptr<int> ip(new int);*ip = 5;fprintf(stdout, "value: %d\n", *ip.get());
} 
如果發生下列任何情況,new表達式拋出std::bad_array_new_length異常,以報告無效的數組長度:(1).數組的長度為負;(2).新數組的總大小超過實現定義的最大值;(3).在一個大括號初始化列表中的初始值設定子句數量超過要初始化的元素數量(即聲明的數組的大小)。只有數組的第一個維度可能會產生這個異常,除第一個維度外的維度都是常量表達式,它們在編譯時檢查。
// 拋出std::bad_array_new_length的三種情況
void test_memory_bad_array_new_length()
{try {int negative = -1;new int[negative]; // 大小為負} catch(const std::bad_array_new_length& e) {fprintf(stderr, "1: %s\n", e.what());}try {int small = 1;new int[small]{1, 2, 3}; // 過多的初始化值設定} catch(const std::bad_array_new_length& e) {fprintf(stderr, "2: %s\n", e.what());}try {int large = INT_MAX;new int[large][1000000]; // 過大} catch(const std::bad_alloc& e) {fprintf(stderr, "3: %s\n", e.what());}
} 
釋放函數:是類的成員函數或全局函數,在一個全局范圍以外的命名空間范圍聲明釋放函數或全局范圍內聲明靜態的釋放函數都是不正確的。每個釋放函數都返回void,并且它的第一個參數是void*。對于這些函數的兩個參數形式,第一個參數是一個指向需要釋放的內存塊的指針,第二個參數是要釋放的字節數。這種形式可能被用于從基類中刪除一個派生類對象。提供給一個釋放函數的第一個參數的值可以是一個空指針值,如果是這樣的話,并且如果釋放函數是標準庫提供的,那么該調用沒有任何作用。
如果提供給標準庫中的一個釋放函數的參數是一個指針,且它不是空指針值,那么釋放函數釋放該指針所引用的存儲,引用指向已釋放存儲(deallocated storage)的任何部分的所有指針無效。使用無效的指針值(包括將它傳遞給一個釋放函數)產生的影響是未定義的。
垃圾回收:在C++中,垃圾回收(自動回收不再被引用的內存區域)是可選的,也就是說,一個垃圾回收器(Garbage Collector, GC)不是必須的。一個垃圾回收器必須能夠識別動態分配的對象的指針,以便它可以確定哪些對象可達(reachable),不應該被回收,哪些對象不可達(unreachable),并可以回收。
4.4 常見的C++內存管理錯誤:常見的與內存管理相關的編程缺陷,包括未能正確處理分配失敗、解引用空指針、寫入已經釋放的內存、對相同的內存釋放多次、不當配對的內存管理函數、未區分標量和數組,以及分配函數使用不當。
未能正確檢查分配失敗:new表達式要么成功要么拋出一個異常。new操作符的nothrow形式在失敗時返回一個空指針,而不是拋出一個異常。
// 未能正確檢查分配失敗
void test_memory_new_wrong_usage()
{// new表達式,要么成功,要么拋出一個異常// 意味著,if條件永遠為真,而else子句永遠不會被執行int* ip = new int;if (ip) { // 條件總是為真} else {// 將永遠不執行}delete ip;// new操作符的nothrow形式在失敗時返回一個空指針,而不是拋出一個異常int* p2 = new(std::nothrow)int;if (p2) {delete p2;} else {fprintf(stderr, "fail to new\n");}
} 
不正確配對的內存管理函數:使用new和delete而不是原始的內存分配和釋放。C內存釋放函數std::free不應該被用于由C++內存分配函數分配的資源,C++內存釋放操作符和函數也不應該被用于由C內存分配函數分配的資源。C++的內存分配和釋放函數分配和釋放內存的方式可能不同于C內存分配和釋放函數分配和釋放內存的方式。因此,在同一資源上混合調用C++內存分配和釋放函數及C內存分配和釋放函數是未定義的行為,并可能會產生災難性的后果。
class Widget {};// 不正確配對的內存管理函數
void test_memory_new_delete_unpaired()
{int* ip = new int(12);free(ip); // 錯誤,應使用delete ipint* ip2 = static_cast<int*>(malloc(sizeof(int)));*ip2 = 12;delete ip2; // 錯誤,應使用free(ip2)// new和delete操作符用于分配和釋放單個對象Widget* w = new Widget();delete w;// new[]和delete[]操作符用于分配和釋放數組Widget* w2 = new Widget[10];delete [] w2;// operator new()分配原始內存,但不調用構造函數std::string* sp = static_cast<std::string*>(operator new(sizeof(std::string)));//delete sp; // 錯誤operator delete (sp); // 正確
} 
在C++的new表達式分配的對象上調用free,因為free不會調用對象的析構函數。這樣的調用可能會導致內存泄漏、不釋放鎖或其它問題,因為析構函數負責釋放對象所使用的資源。
new和delete操作符用于分配和釋放單個對象;new[]和delete[]操作符用于分配和釋放數組。
當分配單個對象時,先調用operator new()函數來分配對象的存儲空間,然后調用其構造函數來初始化它。當一個對象被刪除時,首先調用它的析構函數,然后調用相應的operator delete()函數來釋放該對象所占用的內存。當分配一個對象數組時,先調用operator new[]()對整個數組分配存儲空間,隨后調用對象的構造函數來初始化數組中的每個元素。當刪除一個對象數組時,首先調用數組中的每個對象的析構函數,然后調用operator delete[]()釋放整個數組所占用的內存。要將operator delete()與operator new()一起使用,并將operator delete[]()與operator new[]()一起使用。如果試圖使用operator delete()刪除整個數組,只有數組的第一個元素所占用的內存將被釋放,一個明顯的內存泄漏可能會導致被利用。
new和operator new():可以直接調用operator new()分配原始內存,但不調用構造函數。
函數operator new()、operator new[]()、operator delete()和operator delete[]()都可以被定義為成員函數。它們是隱藏繼承的或命名空間范圍中同名函數的靜態成員函數。與其它內存管理函數一樣,重要的是讓它們正確配對。如果對象所使用的內存不是通過調用operator new()獲得的,同時對其使用operator delete(),就可能發生內存損壞。
多次釋放內存:智能指針是一個類類型(class type),它具有重載的”->”和”*”操作符以表現得像指針。比起原始指針,智能指針往往是一種更安全的選擇,因為它們可以提供原始指針中不存在的增強行為,如垃圾回收、檢查空,而且防止使用在特定情況下不合適或危險的原始指針操作(如指針算術和指針復制)。引用計數智能指針對它們所引用的對象的引用計數進行維護。當引用計數為零時,該對象就被銷毀。
釋放函數拋出一個異常:如果釋放函數通過拋出一個異常終止,那么該行為是未定義的。釋放函數,包括全局的operator delete()函數,它的數組形式與其用戶定義的重載,經常在銷毀類類型的對象時被調用,其中包括作為某個異常結果的棧解開。允許棧解開期間拋出異常導致逃避了調用std::terminate,而導致std::abort函數調用的默認效果。這種情況可能被利用為一種拒絕服務攻擊的機會。因此,釋放函數必須避免拋出異常。
4.5 內存管理器:既管理已分配的內存,也管理已釋放的內存。在大多數操作系統中,包括POSIX系統和Windows,內存管理器作為客戶進程的一部分運行。分配給客戶進程的內存,以及供內部使用而分配的內存,全部位于客戶進程的可尋址內存空間內。操作系統通常提供內存管理器作為它的一部分(通常是libc的一部分)。在較不常見的情況下,編譯器也可以提供替代的內存管理器。內存管理器可以被靜態鏈接在可執行文件中,也可以在運行時確定。
4.6 Doug Lea的內存分配器:GNU C庫和大多數Linux版本(例如Red Hat、Debian)都是將Doug Lea的malloc實現(dlmalloc)作為malloc的默認原生版本。Doug Lea獨立地發布了dlmalloc,其他一些人對其作了修改并用作GNU libc的分配器。
動態分配的內存也可能遭遇緩沖區溢出。例如,緩沖區溢出可被用于破壞內存管理器所使用的數據結構從而能夠執行任意的代碼。
解鏈(unlink)技術:最早由Solar Designer提出。unlink技術被用于利用緩沖區溢出來操縱內存塊的邊界標志,以欺騙unlink()宏向任意位置寫入4字節數據。
4.7 雙重釋放漏洞:Doug Lea的malloc還易于導致雙重釋放漏洞。這種類型的漏洞是由于對同一塊內存釋放兩次造成的(在這兩次釋放之間沒有對內存進行重新分配)。要成功地利用雙重釋放漏洞,有兩個條件必須滿足:被釋放的內存塊必須在內存中獨立存在(也就是說,其相鄰的內存塊必須是已分配的,這樣就不會發生合并操作了),并且該內存所被放入的筐(在dlmalloc中,空閑塊被組織成環形雙鏈表,或筐(bin))必須為空。
寫入已釋放的內存:一個常見的安全缺陷。
RtlHeap:并非只有使用dlmalloc開發的應用程序才可能存在基于堆的漏洞。使用微軟RtlHeap開發的應用程序在內存管理API被誤用時也有可能被利用。與大多數軟件一樣,RtlHeap也在不斷地進化,不同的Windows版本通常都有不同的RtlHeap實現,它們的行為稍有不同。
4.8 緩解策略:有很多緩解措施可以用來消除或減少基于堆的漏洞。
空指針:一個明顯的可以減少C和C++程序中漏洞數量的技術就是在指針所引用的內存被釋放后,將此指針設置為NULL??諔抑羔?執行已釋放內存的指針)可能導致賦寫已釋放內存和雙重釋放漏洞。將指針置為NULL后,任何企圖解引用該指針的操作都會導致致命的錯誤,這樣就增加了在實現和測試過程中發現問題的幾率。并且,如果指針被設置為NULL,內存可以被”釋放”多次而不會導致不良后果。
一致的內存管理約定:(1).使用同樣的模式分配和釋放內存;(2).在同一個模塊中,在同一個抽象層次中分配和釋放內存;(3).讓分配和釋放配對。
隨機化:傳統的malloc函數調用返回的內存分配地址在很大程度上是可預測的。通過讓內存管理程序返回的內存塊地址隨機化,可以使對基于堆的漏洞利用變得更加困難。
運行時分析工具:Valgrind、Purify、Insure++、Application Verifier。
GitHub:https://github.com/fengbingchun/Messy_Test
總結
以上是生活随笔為你收集整理的C和C++安全编码笔记:动态内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: libusb中的热插拔使用举例
 - 下一篇: GitHub/GitLab/Gitee中