看板 C_and_CPP 關於我們 聯絡資訊
恕刪。我懶得畫記憶體配置圖了,特別是這次要說明三個不同之 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
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