【整理】爲何在C/C++中老是對malloc的返回值進行強制轉換


============= 文章1 ================

       首先要說的是,使用 malloc 函數,請包含 stdlib.h(C++ 中能夠是 cstdlib),而不是 malloc.h 。由於 malloc.h 歷來沒有在 C 或者 C++ 標準中出現過!所以並不是全部編譯器都有 malloc.h 這個頭文件。可是全部的 C 編譯器都應該有 stdlib.h 這個頭文件。 

在 C++ 中,強制轉換 malloc() 的返回值是必須的,不然不能經過編譯。
在 C 中,這種強制轉換倒是多餘的,而且不利於代碼維護。 

       起初,C 沒有 void* 指針,那時 char* 被用做泛型指針(generic pointer),因此那時  malloc 的返回值是 char* 。所以,那時必須強制轉換 malloc 的返回值。後來,ISO C 標準定義了 void* 指針做爲新的泛型指針。而且 void* 指針能夠不經轉換,直接賦值給任何類型的指針(函數指針除外)。今後,malloc 的返回值變成了 void* 以後,便再也不須要強制轉換 malloc 的返回值了。如下程序在 VC6 編譯無誤經過。 

#include <stdlib.h> 
int main( void ) 
{ 
    double *p = malloc( sizeof *p ); /* 不推薦用 sizeof( double ) */ 
    return 0; 
}
       固然,強制轉換 malloc 的返回值並無錯,但多此一舉!例如,往後你有可能把 double *p 改爲 int *p 。這時,你就要把全部相關的 (double *)malloc( sizeof(double) ) 改爲 (int *)malloc( sizeof(int) ) 。若是改漏了,那麼你的程序就會存在 bug 。就算你有把握把全部相關的語句都改掉,但這種無聊乏味的工做你不會喜歡吧!不使用強制轉換能夠避免這樣的問題,並且書寫簡便,何樂而不爲呢?使用如下代碼,不管之後指針改爲什麼類型,都不用做任何修改。 
double *p = malloc( sizeof *p );
       值得一提的是,上述寫法中 p 雖然沒有指向具體的內存空間,但並不影響經過 sizeof *p 來計算佔用內存的大小。
       相似地,使用 calloc ,realloc 等返回值爲 void* 的函數時,也不須要強制轉換返回值。


============= 文章2 ================

      本文歸納敘述了上文的內容,而且針對 malloc 返回值的 3 種轉型方式進行總結,(相對於上文)更全面的總結其各自的應用範圍。

之前有篇文章叫《C/C++ 誤區 —— 強制轉換 malloc() 的返回值》(即上文)。文章大體內容是:
  • malloc 函數在 <stdlib.h> 或者 <cstdlib> 頭文件中,而不是 <malloc.h> 中。
  • 因爲 C 語言最初沒有 void 類型,因此是使用 char* 來表明通用指針。
/* the old declaration of malloc */
char* malloc(size_t size);

char* p = malloc( size * sizeof(*p) );
/* 能夠,不須要轉型 */

T1* p1 = malloc(size1 * sizeof(*p1) );
/* (T1!=char) 不能夠,char*不能隱式轉換成T1*  */

T2* p2 = (T2*)malloc(size2 * sizeof(*p2) );
/* (T2!=char) 能夠,顯示類型轉換 */
  • C 語言後來引入了 void 類型,開始使用 void* 表明通用指針,同時規定 void* 能夠隱式轉換到任意指針類型。 
/* the new declaration of malloc */
void* malloc(size_t size);

char* p = malloc( size * sizeof(*p) );
/* 仍然能夠,void* 能夠隱式轉換到任意指針類型 */

T1* p1 = malloc( size1 * sizeof(*p1) );
/* 如今能夠,void* 能夠隱式轉換到任意指針類型 */

T2* p2 = (T1*)malloc( size2 * sizeof(*p2) );
/* 仍然能夠,但再也不必須 */
  • 在引入了 void 以後的 C 語言中,再使用強制轉換是多此一舉,同時影響代碼維護,而且說這是一個 C/C++ 的誤區。  
上面 4 點屬於原文觀點,下面闡述本文觀點。

對 malloc 返回值的轉型,大體有如下三種方式: 
  • 僅在 C 中
/* legal only in C */
/* 新頭文件,具備 void 類型 */
T* p = malloc(size * sizeof(*p) ); /* T!=void */
/* 舊頭文件,不具備 void 類型 */
T* p = (T*)malloc(size* sizeof(*p) ); /* T!=void */
  • 僅在 C++ 中
       C++ 自然支持 void ,可是不容許 void* 隱式轉換到任意類型指針,須要 static_cast 。

// legal only in C++
// 新頭文件
T* p = static_cast<T*>( malloc(size * sizeof(*p) ));
// 舊頭文件(目前還有這種編譯器嗎)
T* p = reinterpret_cast<T*>( malloc(size * sizeof(*p) ));
// 固然在 C++ 中應該考慮
T* p = new T[size];
// 或者
std::vector<T> p(size);
// 但這不是文章討論重點
  • 在 C/C++ 中
/* legal in both C and C++ */
/* legal in both new  and old header */
T* p = (T*)malloc(size * sizeof(*p) );
       第 1 種對新頭文件的轉型方式,如同代碼第 1 行所說,僅在 C 編譯器中合法。由於 C++ 不支持 void* 到其餘指針類型的隱式轉換。因此,原文章說這是 C/C++ 的誤區,並不許確。這僅僅是(引入 void 類型以後的)C 語言中的「非必須」的動做,是不是誤區,還有待考量。
       第 2 種對新舊頭文件的轉型方式,代碼第 1 行也說了,僅在 C++ 編譯器中合法。由於 C 編譯器不認識 static_cast 或者 reinterpret_cast 。
       第3種,是一種中庸的寫法。如同代碼第 1 行所說:此代碼不管是在 C 仍是 C++ 編譯器,不管是新頭文件仍是舊頭文件,都是合法的代碼。是可移植性最好的代碼。 由於代碼中使用的(C 風格的)轉型、malloc,C/C++ 都支持。
       因此,這種寫法並不必定是誤區或者多此一舉。由於代碼的做者也許比原文章的做者對移植性(C 和 C++ 的新舊編譯器)考慮更多。

參考資料:ISO/IEC 9899:1999 (E) Programming languages — C 7.20.3.3 The malloc function
相關文章
相關標籤/搜索