1. 起因
某天,一個同事跟我反饋說在windows上調試公司產品的一個交易核心時出現了使用未初始化的指針導致後台服務崩潰的情況。示例代碼如下所示:
1 struct sample 2 { 3 int* ptr_table[4][4]; 4 //... other members 5 }; 6 7 void test() 8 { 9 sample* sample_ptr = new sample[10]; 10 for (int i = 0; i < 4; i++) 11 sample_ptr[0].ptr_table[0][i] = new int(i); 12 13 // 實際系統中是根據初始化數據對sample_ptr數組中的對象進行賦值,但不是所有的對象都有初始化數據; 14 int* int_ptr = sample_ptr[0].ptr_table[0][0]; 15 if (int_ptr != NULL) 16 { 17 printf("ptr1 = 0x%x\n", int_ptr); 18 *int_ptr = 100; 19 } 20 21 int_ptr = sample_ptr[1].ptr_table[0][0]; 22 if (int_ptr != NULL) 23 { 24 printf("ptr2 = 0x%x\n", int_ptr); 25 *int_ptr = 100; // crashed here! 26 } 27 }
使用未初始化的指針是c++的大忌,但是該代碼在產品發布2年左右的時間一直沒有出現過問題。唯一的區別是發布運行環境是linux,而調試環境是最近才配置好的windows環境。分別在linux下和windows下對代碼進行debug,發現linux下新申請的內存被初始化為0,而windows下新申請的內存卻並未初始化。
將sample* sample_ptr = new sample[10]這行改為sample* sample_ptr = new sample[10]()後兩個系統執行的結果變一樣了,都是被初始化的內存。
那麼問題來了:
(1) 為什麼相同的代碼(new sample[10])在兩個系統下表現形式不一樣呢?是兩個系統的內存分配機制的原因還是類庫的原因?
(2) new sample[10]和new sample[10]()的區別到底是什麼?
2. 研究
在c++中,一般都是以new/delete來申請和釋放內存。對於以下幾種new的用法,各自的區別是什麼呢?
1 int* p1 = new int; 2 int* p2 = new int(); 3 int* p3 = new int(1); 4 // define class A; 5 A* p4 = new A; 6 A* p5 = new A(); 7 A* p6 = new A[10]; 8 A* p7 = new A[10]();
在平時寫代碼的時候,對於一塊新申請的內存我都會要對它進行初始化後才會去用它,一般是例如p3的直接初始化或者memset,因此對於new A和new A()兩種用法的結果還真是不了解之間的區別。
根據c++ manual中5.11節[1] The new and delete Expressions中關於new的描述,new A屬於Default Initializing of Dynamically Allocated Objects(動態創建對象的默認初始化),而new A()則屬於Value Initializing of Dynamically Allocated Objects(動態創建對象的值初始化)。
當為默認初始化操作時,若被創建的對象沒有顯式定義默認構造函數,則按照2.3.4節[2]Variable Initialization Rules的規則進行初始化:
當為值初始化操作時,若被創建的對象沒有顯式定義默認構造函數,則認為對該對象進行初始化操作。
不同new的用法對應的初始化的邏輯總結如下:
new A new A() new A(parameters) A為內置類型 無初始化動作進行值初始化
例A為int類型,則初始化為0
進行值初始化,A被初始化為parameters A為calss/struct調用默認構造函數,A中成員是否
初始化依賴於默認構造函數的實現
若自定義了默認構造函數,則調用自定義的默認構造函數。
否則調用系統默認構造函數,並對A中的成員進行值初始化
調用A的自定義構造函數
因此上面代碼的執行結果分別為:
回到之前的問題,結構體sample是沒有自定義默認構造函數的,按照c++標准,new sample的執行結果是sample中的ptr_table是不會被初始化的。這個在windows上是一致的,可是在linux上為什麼被初始化成了0了呢?在stackoverflow上找到了一個類似的問題[3],其答案為:“Memory coming from the OS will be zeroed for security reasons....the C standard says nothing about this. This is strictly an OS behavior. So this zeroing may or may not be present on systems where security is not a concern”。操作系統的安全機制會在用戶程序申請內存時對分配的內存進行初始化,以防止別有用心的人從新申請到的內存中讀取到敏感數據,例如密碼等,正是由於這個linux的特性使程序在首次申請內存時總是能得到一塊被初始化的內存。
關於這個安全機制我在網上並沒有搜到比較官方的說明文檔,也許是我的搜索方式有誤或者沒有找對關鍵詞,而且發現在程序運行過程中申請的內存也總並不是被初始化了的內存,例如下面的代碼:
1 int size = 10; 2 { 3 int * p1 = new int[size]; 4 for (int i = 0; i < size; i++) 5 { 6 p1[i] = i + 200; 7 printf("%d\t", p1[i]); 8 } 9 printf("\n"); 10 delete []p1; 11 } 12 13 { 14 int * p1 = new int[size]; 15 for (int i = 0; i < size; i++) 16 printf("%d\t", p1[i]); 17 delete []p1; 18 } 19 printf("\n");
執行結果:
希望對這方面有了解的大神能提供一下相關的資料。
3. 總結
(1) 項目中的代碼是明顯不符合c++代碼規范的,在邏輯上會存在使用未經初始化的指針的現象。個人認為變量的初始化不應依賴於編譯器或者系統的實現,而是盡量遵照c++標准或者手工初始化。
(2) 針對class/struct類型,如果沒有自定義默認構造函數,不同的new的用法會產生不同的結果,這個在以後寫代碼的時候要注意。
1:http://shouce.jb51.net/c++/0201721481/ch05lev1sec11.html
2. http://shouce.jb51.net/c++/0201721481/ch02lev1sec3.html#ch02lev2sec13
3:http://stackoverflow.com/questions/8029584/why-does-malloc-initialize-the-values-to-0-in-gcc
本文為原創內容,若有錯誤的地方煩請指正
本文地址:http://www.cnblogs.com/morebread/p/4936441.html