內存表示,位運算及字節序

這篇文章始於對這麼個程序的思考:

 

int main(int argc, char* argv[])

{

     int i=1234567;

     DWORD dwWrite;

     HANDLE hFile = CreateFile("test.txt", GENERIC_WRITE, FILE_SHARE_READ,

         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

     WriteFile(hFile, &i, sizeof(int), &dwWrite, NULL);

     CloseHandle(hFile);

     return 0;

}

 

用二進制編輯器(如UltraEdit)打開程序輸出結果文件“test.txt”查看,結果是:“87 D6 12 00”。如果將87D61200直接輸入計算器,轉變成十進制數,那就是2278953472,並非我們所期待的1234567,但如果把這幾個十六進制數倒過來,輸入0012D687到計算器,轉成十進制數,就出現了我們想要的結果,1234567。這是一個字節序的問題,但在討論這個之前,我想先來討論下內存的表示。

 

粗一想,這根本不是問題,把內存地址和數據以表的形式列出來不就行了麼?但仔細一想,確實還有些要斟酌之處。因爲:我們對內存“寫生”到紙上就有上下左右方位之分,而真實的內存,並沒有上下左右這些方位的概念,只有“高位”和“低位”的概念,內存電子元器件所表示的那些“0”和“1”是連續的。那究竟把高位畫在上還是畫在下?或者左?或者右?下面我來說說我的理解,按照我們常規思維,“高”通常就代表“上”,“低”就代表“下”,不僅我們中國人如此,發明電腦的老美也如此,DOS的內存管理中有個“上位內存”的概念,上位內存英文就是“Upper Memory Block”,居高位,在常規內存之上的意思,看來上高下低是沒什麼問題了吧。

那如果我們有張紙條,必須要左右繪表來描述內存,那究竟左高還是右高?其實這也是比較明顯的,像1234567這個整數,1明顯居高位,它是最有效力的數字,它在最左邊,所以應該用左邊來表示高位,這樣和我們的思維習慣比較相符,如果這個理由還不夠充分,那我這裏就插入“位運算”來講一下。

 

int main(int argc, char* argv[])

{

     unsigned char c, d, e;

     c=38;     //c ==   100110 (binary)

     d=c>>1;   //d ==    10011 (binary)

     e=c<<1;   //e ==  1001100 (binary)

     return 0;

}

 

>>”是右位移運算符(bitwise right shift operator),“<<”是左位移運算符(bitwise left shift operator),而從這個簡單的程序上看來,它們確實執行了我們常規思維中所理解的左右位移,左移使得低位變爲高位,“增值”,右移使得高位變爲低位,“貶值”,看來左邊表示高位,右邊表示低位,也是沒有什麼疑義的了。

 

但!你發現沒有,這種表示規則和我們的閱讀習慣,書寫習慣完全相反。我們人類寫字都是從左到右,從上到下,(BTWOK,我承認日本人有從右到左的習慣,但這裏就別鑽牛角尖了,嘿嘿)如何見得這個衝突?看下面的代碼:

 

char szArray[10] ={'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', '/0'};

 

我們給szArray這個數組賦初值,這麼一來,你認爲szArray[0]szArray[1]分別是哪個字符?那還用想麼?當然分別是“a”和“b”,那麼szArray[0]居低位,還是szArray[1]居低位?那也根本不用想,當然是szArray[0]居低位,但回頭看看我們的賦值語句,“a”是寫在“b”的左邊的,但它卻居低位而非高位。所以我認爲人的習慣還是從上往下,從左往右,把低的東西寫到高,把小的東西寫到大。至於爲什麼會這樣,我也無法回答,可能要去請教下生物學家。看下面這個截圖,正好說明了這點,這是VC++中的內存表示法,明顯和我前面推薦的表示法相反,低位居上居左,高位居下居右。

那說了半天究竟要用哪種表示法?怎樣?知道這個是個問題了吧。我這裏也沒有標準答案,事實上兩種表示法都會出現在實際應用中,所以都需要適應。但有一點必須強調,要不就上左高位,要不就上左低位,不存在上高左低或上低左高。

 

好了,回到文章開始的那個字節序的問題,我曾經被這個字節序的問題弄得糊里糊塗,現在終於想明白,其實我當時之所以這麼難明白,主要就是字節序這個英文單詞(endian)太能誤導人。endian這個單詞在傳統詞典裏是查不到的,但看到前邊的“end”就容易讓人想起“結束”,這真是個大錯誤,其實它和結束沒有任何關係,看它的英文解釋:The ordering of bytes in a multi-byte number,翻譯爲“字節序”就合適了。

 

目前有兩種常見的字節序,即“big-endian”和“little-endian”,如果你認爲“endian”含有“結束”的意思的話這裏就出差錯了,因爲如果內存用“開始”和“結束”來描述本來就是很不清晰的,只能用“高位”和“低位”來描述,“big-endian”是把最有效力的數放在低位(這裏姑且把“低位”算“開始”吧),而“little-endian”正好相反,把最沒效力的數放在低位。

 

字節序的採用和系統有關係,目前PC機上的系統都是“little-endian”,很明顯,我調試開篇程序的這個系統使用的也是“little-endian”,據稱Mac機則使用“big-endian”,可惜我只用過PC機,所以沒法在此證實了。

 

由於各種系統使用的字節序可能不同,系統對數字的理解就有可能不同,那如果系統之間傳輸數字信息的時候就可能出亂子,比如我用PC機把整型數“1234567”傳輸到一臺Mac機去,Mac機就會把這個數字理解成“2278953472”,相差不是一點點。所以這裏就涉及到字節序轉換的問題。也就是那幾個常用的函數:htonshtonlntohsntohl。這裏就不展開說了。最後還有個問題,假如兩個系統用的都是同一種字節序,那在它們之間傳輸數據是否還需要用這幾個函數轉換字節序?答案是最好用,因爲網絡也有它自己的字節序(big-endian),一些包在網上傳輸的時候可能要設置一些要讓網絡理解的參數,這種轉換還是必須的,儘管有時候看起來不轉換也沒問題,但大家都依循這個規則不是更好麼?

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章