2015-05-23 149 views
0

我正在通過here,發現malloc可能會導致不需要的行爲,如果我們不包括stdlib.h,轉換返回值,如果指針和整數大小在系統上不同。malloc返回類型混淆

下面是該問題給出的代碼片段。這是在指針和整數大小不同的64位機器上嘗試的。

int main() 
{ 
     int* p; 
     p = (int*)malloc(sizeof(int)); 
     *p = 10; 
     return 0; 
} 

如果我們不包括stdlib.h,編譯器將承擔malloc返回類型爲int,鑄造,並分配給不同大小的指針可能會導致不必要的行爲。 但我的問題是爲什麼鑄造intint*並將其分配給不同大小的指針可能會導致此問題。

+1

首先,它被(隱式)轉換爲「int」,從而從8個字節截斷爲4個字節。除非原始8字節值的第一個(更重要的)4個字節包含全零,否則某些信息會丟失。將結果轉換回'int *'不會檢索到這些信息,並留下一個「假」指針。 –

+0

當編譯器遇到'int * p'聲明時,它會在聲明時確定此指針需要8個字節(例如)的存儲空間。接下來,在運行時,執行malloc時,它會返回一個默認的'int',因爲沒有函數原型聲明存在(由於缺少stdlib.h)。當新分配的內存的地址(8個字節)存儲在一個int(4個字節)並返回給您時,可能會失去重要性。現在,在這一點上,如果您嘗試將int轉換爲int *並將其分配給您的指針'p',那麼可能會出現意想不到的行爲。 – iammowgli

回答

1

幾乎任何函數都會導致未定義的行爲,如果在調用它之前沒有給出原型,除非例如它的原型是int function(int x);

但是很明顯,如果一個指針的大小比的intmalloc()規模較大返回int,因爲隱式聲明的,則返回的地址可能不是真正的地址,因爲例如,它可能不是可以用較少的位來表示它。

解引用它將是未定義的行爲,順便說一下,你不能測試,因爲它是未定義的,你會期望發生什麼?它是未定義的!

那麼,沒有什麼可以在那裏測試?

5
int main() 
{ 
     int* p; 
     p = (int*)malloc(sizeof(int)); 
     *p = 10; 
     return 0; 
} 

在C99和C2011規則,調用malloc沒有可見的聲明是一個約束違反,這意味着符合編譯器必須發出診斷。 (這與C接近說有些東西是「非法的」)。如果你的編譯器沒有警告這個調用,你應該找出使用它的選項。

根據C90規則,調用沒有可見聲明的函數會導致編譯器假定該函數實際上返回類型爲int的結果。由於malloc實際上定義爲返回類型爲void*,因此行爲未定義;編譯器不需要進行診斷,但標準完全沒有說明在調用評估時會發生什麼。

什麼通常發生在實踐中是,編譯器生成的代碼彷彿malloc被定義爲返回一個int結果。例如,malloc可能會將其64位void*結果放在某個特定的CPU寄存器中,並且調用代碼可能爲假定該寄存器包含一個32位的int。 (這不是一種類型轉換;它只是錯誤的代碼,錯誤地將一種類型的值看作是不同類型的值。)(可能是垃圾)int值然後是轉換爲int*並存儲在p中。你可能失去高位返回指針的低位32位 - 但這只是一個可以出錯的任意方式。

malloc可能會將其64位結果壓入堆棧,調用者可能只彈出堆棧中的32位,導致堆棧未對齊,從而導致所有後續執行不正確。由於歷史原因,C編譯器通常不使用這種調用約定,但標準允許它。

如果intvoid*int*都發生在相同的大小(因爲他們經常是在32位系統),代碼是可能的工作 - 但即使這不能保證。例如,調用約定可能會使用一個寄存器來返回int結果,而另一個返回指針結果。再次,大多數現有的C調用約定允許使用這樣的假設的舊的錯誤代碼。

調用malloc需要#include <stdlib.h>,即使有些編譯器可能不會執行該要求。添加#include(並放下演員)要比花時間思考如果沒有時會發生什麼更容易。