作者tropical72 (藍影)
看板C_and_CPP
標題Re: [問題] 有無malloc?
時間Thu Nov 10 16:24:32 2011
恕刪。我懶得畫記憶體配置圖了,特別是這次要說明三個不同之 sub function。
口述若不清楚請再反應。
假設 main 裡,
short x = 0xffff,看三份副函式之呼叫情況。
void f0(short x2)
{
unsigned *p = (unsigned*)malloc(sizeof(unsigned));
*p = *(unsigned*)&x2;
printf("*p=%08x\n", *p);
*p = 0x12345678;
}
int main() {
short x = 0xffff;
short y = 0xffff;
f0(x);
}
(1)
主程式裡 f0(x)
f0 是做 pass by value,意指在記憶體裡面,會再多配置一個空間給 x2,
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□ ---> 位置開頭為 0x20 (x2_value = 0xff 0xff)
(2)
unsigned *p = (unsigned*)malloc(sizeof(unsigned));
爾後 p 馬上配置一個 unsigned 的空間,也就是 4bytes。
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□ ---> 位置開頭為 0x20 (x2_value = 0xff 0xff)
副程式 p □□□□ ---> 配置開頭為 0x40 ( p = 0x40,剛配置的位置)
(3)
*p = *(unsigned*)&x2;
直接將 (x2 記憶體位置值)給 (p指向位置值),但由於 p 是 pointer to unsigned,
所以一次會給 4bytes,至於原本的 x2 不到 4 bytes,就向後抓到二個問號。
注意到,pointer 抓值的原則是,以該 pointer 指向之資料型別為一 size,
不足並不會補零進來。
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0xff 0xff)
|<---->|
p 取出空間
副程式 p □□□□ ---> 配置開頭為 0x40 ( p = 0x40,剛配置的位置)
(*p = 0x????ffff)
(4)
printf("*p=%08x\n", *p);
這裡又是另一個小問題了,看第三步驟的圖,有沒有發現,
為什麼 *p 我是寫
0x????ffff,而不是寫
0xffff????
原因是:
little endian。至於輸出結果如果剛好為 0x0000ffff的話,
那就別意外了,只是湊巧裡面的 ?? ?? 剛好都是零而已,原因是,
當 pointer 在做 dereference 時,一次取幾個 bytes ,視該 pointer 型態而定,
由於 pointer 型態是指向 unsigned,故抓了 4 bytes 。
(5)
*p = 0x12345678;
輸出完後,再對 *p 做 assigned value,*p=0x12345678;
實際 assign 到的是這塊
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0xff 0xff)
副程式 p □□□□ ---> 配置開頭為 0x40 ( p = 0x40,剛配置的位置)
(*p = 0x12 0x34 0x56 0x78)
配置到的是 p 配出來的記憶體空間,所以它不會動作主程式的 x ,
也不會動到副程式的 x2,最後它的值是從
0x????ffff 變成 0x12345678 而已。
(6) 而
副函式結束時,x2 變數會被系統收回去,但 p 是用 malloc 出來的,
系統收不回去,造成了 memory leak,那塊 0x40 (p配置出來的) 就死在那。
----
void f1(short x2)
{
unsigned *p = (unsigned*)&x2;
printf("*p = %08x\n", *p);
*p = 0xffff5678;
}
這例子和剛剛相似,
(1)
主程式裡 f1(x);
當主程式之 x 傳遞指數到 f1 時,x2 會是另一塊空間,是一個副本
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□ ---> 位置開頭為 0x20 (x2_value = 0xff 0xff)
(2)
unsigned *p = (unsigned*)&x2;
設一個指標變數 p ,裡面存的是 x2 之位置值
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□???? ---> 位置開頭為
0x20 (x2_value = 0xff 0xff)
副程式 p □□□□ --->
存 0x20, 即 x2 之位置
(3)
printf("*p = %08x\n", *p);
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□???? ---> 位置開頭為
0x20 (x2_value = 0xff 0xff)
|<---->|
p 取出空間
副程式 p □□□□ --->
存 0x20, 即 x2 之位置
目前 p 是 unsigned pointer 型態,當做 *p 時,將去位置 0x20 (也就是 x2位置)
取出裡面的值出來。承上述原則,當用 *p 做 deference 時,由於 p 為
unsigned pointer ,所以一次讀出 4 bytes 出來,又因 little endian,
所以顯示為
0x????ffff 。
(4)
*p = 0xffff5678;
這就是一個
非常危險的動作了,它主要是將副函式的 x2
以 4 bytes 方式寫入 0x
ffff5678。可能有些人會以為,寫出去的結果是
副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0x
ffff)
ffff5678
因為 x2 原本就是 0xffff, 所以寫入只改變了 ???? 這數值變 5678,
這是錯的!因為寫入的時候,還是以 little endian 方式寫入
副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0x
5678)
5678ffff
這樣改變的就不只是問號,連原本前半段看起來與 x2 一樣,但實際上
寫入不一樣,導致整個記憶體錯亂。
另,若
???? 本身記憶體位置,
(a) 有其它變數在使用 --> 改寫了程式碼中其它變數,程式出包。
(b) 留給系統核心使用 --> 意思是會跳出「記憶體0x5f6ebacf為唯讀」之類的錯誤
都會導致出包。
(5) 最後 f1 副程式結束,所有記憶體收回。
主程式 x □□ ---> 位置開頭為 0x12 ( x_value = 0xff 0xff)
副程式 x2 □□???? ---> 回收
副程式 p □□□□ ---> 回收
但所謂的收回,並不代表會把 x2 與 p 直接清空,只是由作業系統
標示「這二個空間目前沒人在用」而已,
然而如果原本的 ????
是主程式裡面配置的變數,那會發生什麼事情就真的沒人知道了。
(a) 從副程式收回 ???? 的觀點看來, ???? 目前沒人用,
要等到其他程式碼再宣告變數,恰巧 os 又配置到 ???? 時,才可復用。
(b) 從主程式觀念看來,一開始宣告變數後,在變數生命週期裡都可使用。
不覺得 (a)、(b) 整個就是相互衝突嗎?
-----
void f2(short *x2)
{
unsigned *p = (unsigned*)x2;
printf("*p = %08x\n", *p);
*p = 0xffff5678;
}
short x=0xffff;
f2(&x);
(1)
呼叫 f2(&x)
副函式之 x2 為指標,呼叫時存的是 x 之位置值。
注意, x2 是 pointer, size 是固定的,
這裡做普遍性假設為 4bytes。
主程式 x □□ ---> 位置開頭為
0x12345678,其值為 0xffff
副程式 x2 □□□□ ---> x2存之值為 (x2_value =
0x12345678)
(2)
unsigned *p = (unsigned*)x2;
副函式再宣告一個指標,同時將 x2 裡面的內容,直接丟給 p。
主程式 x □□
???? ---> 位置開頭為
0x12345678,其值為 0xffff
副程式 x2 □□□□ ---> x2存之值為 (x2_value =
0x12345678)
副程式 p □□□□ ---> p存之值為 ( p_value =
0x12345678)
(3)
printf("*p = %08x\n", *p);
p 去找 0x12345678 這個位置,由於為 unsigned pointer,
所以一次抓 4 bytes (sizeof(unsigned)=4) 出來解讀,
再考慮 little endia 問題,所以解讀到的是
0x????ffff
再強調一次,如果輸出 0x0000ffff 不要認為是補零,
是「恰巧」後面的 ???? 是 0x0000 而已。
(4)
*p = 0xffff5678;
再將 0xffff5678 這值寫入 (p 所指向之位置) 中,即 0x12345678 ,
也就是主程式裡面的 x,再考慮 little endian 問題
0x
5678ffff
主程式 x □□
???? ---> 位置開頭為
0x12345678,其值為 0xffff
副程式 x2 □□□□ ---> x2存之值為 (x2_value =
0x12345678)
副程式 p □□□□ ---> x2存之值為 ( p_value =
0x12345678)
(5) 副函式 f2 結束, 裡面的 x2, p 被收回,
最後不只改變了一塊莫名的 ???? ,這次連主程式裡面的 x 都改掉了。
----
上面該講的應該都講了,為求正確性,
little endian 講很多,這部份不熟的話再去翻翻書補起來。
--
No matter how gifted you are,
alone, can not change the world.
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 180.177.78.41
推 cutecpu:有種看 t 大發文比看書還好看的 fu 11/10 16:56
→ diabloevagto:感謝分享! 11/10 17:04
推 windincloud:不推不行呀!! 11/10 17:05
→ angleevil:我沒看得很仔細,但是有一個問題請教一下,short x=0xffff 11/10 17:06
→ angleevil:x得到的值應該是0xffff,與實際記憶體位置無關吧? 11/10 17:06
是的,short x=0xffff; 但如果再多這兩行的話
unsigned *p = (unsigned*)&x;
printf("%08x\n", *p); // 到這裡 x 值都不會變, 因為只有 read
*p = 0xffff5678; // 這行就會改變 x 值
最後 x 會變 0x5678, little endian 問題。
→ diabloevagto:請問這些問題是pointer會出現的,那如果用reference 11/10 17:06
→ diabloevagto:還會有這些問題出現嗎?譬如轉型取值會超出範圍 11/10 17:07
c++ reference 不會有這問題,原因在於 reference 不能向 pointer 這樣亂搞。
short a=10;
int &b = static_cast<int>(a);
上面這段光在 compiler 那裡就出 error,所以想亂搞也沒辦法。
→ angleevil:diabloevagto...你不認真. 11/10 17:10
→ diabloevagto:x是變數,得到的就是裡面的數值,跟記憶體位址無關 11/10 17:10
→ diabloevagto:a大請指教>< 11/10 17:10
→ angleevil:抱歉,我也不認真.再看一次才發現是變數值 11/10 17:14
推 diabloevagto:t大怎麼什麼都會...太神了 11/10 17:25
→ tropical72:哪裡,我會的都是皮毛,而且都是版友不吝指導的。 11/10 17:29
→ angleevil:喔,我現在才看到a,b的意思. 看來隨意轉值問題很大 11/10 17:31
→ diabloevagto:請問我指標如果一定要小轉大,是否有安全的辦法呢? 11/10 17:32
→ diabloevagto:如果先轉型傳到一個變數,在用指標去讀是否可行 11/10 17:33
→ james732:為什麼會有這樣的需求? 11/10 17:34
由小轉大 : short* ---> unsigned* ,目前還真沒看過有這種做法,頂多只有這樣 :
short x[8]={1,2,3,4};
unsigned long long *p = (unsigned long long*)&x[0];
*p=0ULL;
本來要設 4 次 0,現在只要設一次就好,但這作法是安全的,
除了 array、struct(不考慮 padding) 這種保證連續配置空間連續之條件,
其它沒什麼方式可保證可以安全執行。因那個 ???? 可能是從頭到尾連
「訪問」都不能「訪問」的,只要一訪問就出包。
除了這個例外之外,其他的全部都是二種做法
(1) 直接挑一樣的 size :
像要觀查 float bit ,就用 unsigned* 去接
(2) 直接用 unsigned char* :
這方法除了傳 address 外,還要再傳 bytes 數,
而且因為 little endian 關係,還要從後面翻譯回來 。
比較特例的是這個東西
unsigned long long x=0x1234567890123456;
printf("%016llx\n", x); // C99 以後支援, VC 不支援。
最後我用 unsigned 處理
unsigned *p = (unsigned*)&x ;
printf("%08x%08x\n", p[1], p[0]); // for little endian
這算是我看過 (不是用一樣size,也不是用 unsigned char) 特例。
※ 編輯: tropical72 來自: 180.177.78.41 (11/10 17:53)
→ diabloevagto:只是突然想到這個問題,其實也沒實際需求... 11/10 17:35
→ james732:那就不用在意啦 XD (其實我只是懶得想) 11/10 17:36
→ angleevil:我的想法很kiss.指標本來就是指相同型別的變數.那麼你要 11/10 17:38
→ angleevil:這樣搞時.即使先用一個暫時變數(和指標一樣的型態). 11/10 17:40
→ angleevil:然後去改變指標指向的值.可是那也只是改到暫時的變數 11/10 17:42
→ angleevil:跟原來想做的有關連嘛? 11/10 17:43
→ diabloevagto:我想的也跟a大的一樣,但ptr會先new一個空間出來,然 11/10 17:46
→ diabloevagto:後填入中間暫存的變數的值,這樣可以預防轉型讀到? 11/10 17:47
→ diabloevagto:數值的部份,單純要從一個short轉成int 11/10 17:47
→ diabloevagto:dynamic_cast能否用在這種情況? 11/10 17:48
→ diabloevagto:上上行補充,是short*跟int* 11/10 17:49
→ angleevil:dynamic_cast是用在基底類別指標轉換為衍生類別指標 11/10 17:51
→ angleevil:int和short是獨立單元.實際上此做法只是單純解決short 11/10 17:52
→ angleevil:轉成int. 可是當暫時變數的值傳回x2時.依舊會出問題 11/10 17:53
→ angleevil:數值運算、賦值或比較中不可以隨意混用不同型別的數值 11/10 17:54
→ angleevil:13戒是有歷史的阿.我只能說隨意拉.太難了 11/10 17:55
推 xatier:t 大出書吧! 11/10 22:07
推 thank1984:謝謝t大 辛苦你了 這篇真的對我幫助很大 11/10 23:14
推 VictorTom:推:) 11/10 23:46
推 leonjye:先拜一下再來細看 超強 11/11 11:05
推 siriusu:推推推 12/08 17:38