Re: [問題] 有無malloc?

看板C_and_CPP作者 (藍影)時間14年前 (2011/11/10 16:24), 編輯推噓8(8030)
留言38則, 11人參與, 最新討論串2/2 (看更多)
恕刪。我懶得畫記憶體配置圖了,特別是這次要說明三個不同之 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 方式寫入 0xffff5678。可能有些人會以為,寫出去的結果是 副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0xffff) ffff5678 因為 x2 原本就是 0xffff, 所以寫入只改變了 ???? 這數值變 5678, 這是錯的!因為寫入的時候,還是以 little endian 方式寫入 副程式 x2 □□???? ---> 位置開頭為 0x20 (x2_value = 0x5678) 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 問題 0x5678ffff 主程式 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

11/10 16:56, , 1F
有種看 t 大發文比看書還好看的 fu
11/10 16:56, 1F

11/10 17:04, , 2F
感謝分享!
11/10 17:04, 2F

11/10 17:05, , 3F
不推不行呀!!
11/10 17:05, 3F

11/10 17:06, , 4F
我沒看得很仔細,但是有一個問題請教一下,short x=0xffff
11/10 17:06, 4F

11/10 17:06, , 5F
x得到的值應該是0xffff,與實際記憶體位置無關吧?
11/10 17:06, 5F
是的,short x=0xffff; 但如果再多這兩行的話 unsigned *p = (unsigned*)&x; printf("%08x\n", *p); // 到這裡 x 值都不會變, 因為只有 read *p = 0xffff5678; // 這行就會改變 x 值 最後 x 會變 0x5678, little endian 問題。

11/10 17:06, , 6F
請問這些問題是pointer會出現的,那如果用reference
11/10 17:06, 6F

11/10 17:07, , 7F
還會有這些問題出現嗎?譬如轉型取值會超出範圍
11/10 17:07, 7F
c++ reference 不會有這問題,原因在於 reference 不能向 pointer 這樣亂搞。 short a=10; int &b = static_cast<int>(a); 上面這段光在 compiler 那裡就出 error,所以想亂搞也沒辦法。

11/10 17:10, , 8F
diabloevagto...你不認真.
11/10 17:10, 8F

11/10 17:10, , 9F
x是變數,得到的就是裡面的數值,跟記憶體位址無關
11/10 17:10, 9F

11/10 17:10, , 10F
a大請指教><
11/10 17:10, 10F

11/10 17:14, , 11F
抱歉,我也不認真.再看一次才發現是變數值
11/10 17:14, 11F

11/10 17:25, , 12F
t大怎麼什麼都會...太神了
11/10 17:25, 12F

11/10 17:29, , 13F
哪裡,我會的都是皮毛,而且都是版友不吝指導的。
11/10 17:29, 13F

11/10 17:31, , 14F
喔,我現在才看到a,b的意思. 看來隨意轉值問題很大
11/10 17:31, 14F

11/10 17:32, , 15F
請問我指標如果一定要小轉大,是否有安全的辦法呢?
11/10 17:32, 15F

11/10 17:33, , 16F
如果先轉型傳到一個變數,在用指標去讀是否可行
11/10 17:33, 16F

11/10 17:34, , 17F
為什麼會有這樣的需求?
11/10 17:34, 17F
由小轉大 : 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)

11/10 17:35, , 18F
只是突然想到這個問題,其實也沒實際需求...
11/10 17:35, 18F

11/10 17:36, , 19F
那就不用在意啦 XD (其實我只是懶得想)
11/10 17:36, 19F

11/10 17:38, , 20F
我的想法很kiss.指標本來就是指相同型別的變數.那麼你要
11/10 17:38, 20F

11/10 17:40, , 21F
這樣搞時.即使先用一個暫時變數(和指標一樣的型態).
11/10 17:40, 21F

11/10 17:42, , 22F
然後去改變指標指向的值.可是那也只是改到暫時的變數
11/10 17:42, 22F

11/10 17:43, , 23F
跟原來想做的有關連嘛?
11/10 17:43, 23F

11/10 17:46, , 24F
我想的也跟a大的一樣,但ptr會先new一個空間出來,然
11/10 17:46, 24F

11/10 17:47, , 25F
後填入中間暫存的變數的值,這樣可以預防轉型讀到?
11/10 17:47, 25F

11/10 17:47, , 26F
數值的部份,單純要從一個short轉成int
11/10 17:47, 26F

11/10 17:48, , 27F
dynamic_cast能否用在這種情況?
11/10 17:48, 27F

11/10 17:49, , 28F
上上行補充,是short*跟int*
11/10 17:49, 28F

11/10 17:51, , 29F
dynamic_cast是用在基底類別指標轉換為衍生類別指標
11/10 17:51, 29F

11/10 17:52, , 30F
int和short是獨立單元.實際上此做法只是單純解決short
11/10 17:52, 30F

11/10 17:53, , 31F
轉成int. 可是當暫時變數的值傳回x2時.依舊會出問題
11/10 17:53, 31F

11/10 17:54, , 32F
數值運算、賦值或比較中不可以隨意混用不同型別的數值
11/10 17:54, 32F

11/10 17:55, , 33F
13戒是有歷史的阿.我只能說隨意拉.太難了
11/10 17:55, 33F

11/10 22:07, , 34F
t 大出書吧!
11/10 22:07, 34F

11/10 23:14, , 35F
謝謝t大 辛苦你了 這篇真的對我幫助很大
11/10 23:14, 35F

11/10 23:46, , 36F
推:)
11/10 23:46, 36F

11/11 11:05, , 37F
先拜一下再來細看 超強
11/11 11:05, 37F

12/08 17:38, , 38F
推推推
12/08 17:38, 38F
文章代碼(AID): #1Ekuf5w4 (C_and_CPP)
文章代碼(AID): #1Ekuf5w4 (C_and_CPP)