部分1:大端小端概念
摘自:https://jocent.me/2017/07/25/big-little-endian.html
計算機系統中內存是以字節爲單位進行編址的,每個地址單元都唯一的對應着1個字節(8 bit)。這可以應對char類型數據的存儲要求,因爲char類型長度剛好是1個字節,但是有些類型的長度是超過1個字節的(字符串雖然是多字節的,但它本質是由一個個char類型組成的類似數組的結構而已),比如C/C++中,short類型一般是2個字節,int類型一般4個字節等。因此這裏就存在着一個如何安排多個字節數據中各字節存放順序的問題。正是因爲不同的安排順序導致了大端存儲模式和小端存儲模式的存在。
1. 概述
1.1 定義
假如有一個4字節的數據爲 0x12345678(十進制:305419896,0x12爲高字節,0x78爲低字節),若將其存放於地址 0x4000 8000中,則有:
內存地址 0x4000 8000(低地址) 0x4000 8001 0x4000 8002 0x4000 8003(高地址)
大端模式 0x12(高字節) 0x34 0x56 0x78(低字節)
小端模式 0x78(低字節) 0x56 0x34 0x12(高字節)
明顯可以看出規律,即大端“高低低高”,小端“高高低低”:
大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中
小端模式,是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中
說明:0x代表16進制,由於16爲2的4次方,剛好可以用4位半個字節表示;,或者說兩個16進制數用一個字節表示。所以對於16進制的存儲可以說:一個16進制數佔4位,或者說兩個16進制數佔一個字節。比如對於int 0x12345678,0x12、0x34、0x56、0x78各佔一個字節,直接翻譯二進制爲0001、0010,0011、0100,0101、0110,0111、1111;這種翻譯方法與將0x12345678先轉爲10進制,再換算爲二進制結果一致。
1.2 特點
爲什麼截然相反的大小端存儲模式能夠並存至今?在標準化備受推崇的今天,爲什麼大小端誰都沒有被另外一個所同化?我想這除了歷史的慣性使然,還與它們各自的優缺點有關。
大端模式優點:符號位在所表示的數據的內存的第一個字節中,便於快速判斷數據的正負和大小
小端模式優點:1. 內存的低地址處存放低字節,所以在強制轉換數據時不需要調整字節的內容(註解:比如把int的4字節強制轉換成short的2字節時,就直接把int數據存儲的前兩個字節給short就行,因爲其前兩個字節剛好就是最低的兩個字節,符合轉換邏輯); 2. CPU做數值運算時從內存中依順序依次從低位到高位取數據進行運算,直到最後刷新最高位的符號位,這樣的運算方式會更高效
其各自的優點就是對方的缺點,正因爲兩者彼此不分伯仲,再加上一些硬件廠商的堅持(見1.3節),因此在多字節存儲順序上始終沒有一個統一的標準
1.3 現狀
Intel的80×86系列芯片使用小端存儲模式
ARM芯片默認採用小端,但可以切換爲大端
MIPS芯片採用大端,但可以在大小端之間切換
在網絡上傳輸的數據普遍採用的都是大端
2. 判斷
方法一:通過將多字節數據強制類型轉換成單字節數據,再通過判斷起始存儲位置是數據高字節還是低字節進行檢測
// @Ret: 大端,返回true; 小端,返回false
bool IsBigEndian_1()
{
int nNum = 0x12345678;
char cLowAddressValue = *(char*)&nNum;
// 低地址處是高字節,則爲大端
if ( cLowAddressValue == 0x12 ) return true;
return false;
}
方法二:利用聯合體union的存放順序是所有成員都從低地址開始存放這一特性進行檢測
// @Ret: 大端,返回true; 小端,返回false
bool isBigEndian_2()
{
union uendian
{
int nNum;
char cLowAddressValue;
};
uendian u;
u.nNum = 0x12345678;
if ( u.cLowAddressValue == 0x12 ) return true;
return false;
}
3. 轉換
3.1 大小端轉換
// 實現16bit的數據之間的大小端轉換
#define BLSWITCH16(A) ( ( ( (uint16)(A) & 0xff00 ) >> 8 ) | \
( ( (uint16)(A) & 0x00ff ) << 8 ) )
// 實現32bit的數據之間的大小端轉換
#define BLSWITCH32(A) ( ( ( (uint32)(A) & 0xff000000) >> 24) |\
(((uint32)(A) & 0x00ff0000) >> 8) | \
(((unit32)(A) & 0x0000ff00) << 8) | \
(((uint32)(A) & 0x000000ff) << 32) )
3.2 網絡字節序與主機字節序的轉換
方法三:使用內存查看器
轉自:https://blog.csdn.net/shuiniu1224/article/details/21997221
查看內存是使用VS2010進行編碼的一個非常基本的技能了,快速而準確地查看內存,可以幫助你準確分析代碼中各變量的取值,以及存儲狀態,幫助你發現程序中的BUG,改進代碼的健壯性。
如何查看內存?繼續採用以上的例程進行說明,將程序F5到第13行,再單步到下一句
圖3
按下ALT+6,此時我們可以看到內存1的窗口,我們從自動窗口中先找到指針p的地址,然後將地址複製到內存地址欄中,回車,即可看到此時地址中的值。內存窗口中左邊的灰色值代表地址,右邊則表示地址中存儲的值。我們可以看到p地址對應的值爲03,但後面還有000000跟着,其實因爲我們存儲的是一個整數值,需要4個字節存儲,因此就算P中結果是3,也同樣佔用了4個字節。
這裏還需要注意的一個概念是,大端法存儲和小端法存儲的概念。回到上面圖中我們可以看出,內存地址從左至右,從上至下是依次增大的。我們這個值3其實正確的讀法應該是從右至左讀取的,即0x00000003,03是在最低位,而03也是存儲在內存地址中的低地址中的,因此這是小端法存儲,大端法則剛好相反。需要了解這方面更多信息的人,一定要上網查找更多資料多學習,本文就不再詳述。
部分2:數字存儲與大端小端結合
例題1
union Aunion
{
short k;
char t[2];
}val;
int main()
{
val.t[0] = 5;
val.t[1] = 1;
printf("%d", val.k);
return 0;
}
輸出的值是多少?
5的二進制爲0000 0101,1二進制爲0000 0001,所以排列順序爲0000 0101 0000 0001,(內存中:地址由小到大,並且每個地址單元都唯一的對應着1個字節(8 bit))。若爲小端,則val.k在取出時,5、1的二進制被解釋爲0000 0001 0000 0101,其10進制數爲261;若爲大端,則val.k在取出時,5、1被解釋爲0000 0101 0000 0001,位2861。
例題2
int main()
{
char a = -1;
unsigned char b = -1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
求輸出到屏幕的數
首先明確整數的存儲方式:正整數直接存儲其二進制形式,位數不夠時,在其二進制數左邊補0;對於負整數,先求出其正數的二進制,然後對此二進制數求補碼即可。
所以對於-1計算方法爲:
1的二進制 :0000 0001
對上述求補碼:1111 1111
所以字符a、b都是存儲的1111 1111,由於a是有符號的char,故取1111 1111補碼,爲0000 0001,輸出1;b是無符號的char,故輸出爲1111 1111對應的數字255。
float、double的存儲方式:先將該數轉爲對應的二進制,在寫成二進制對應的科學計數法,之後存儲該數的三部分信息,分別是符號、指數、尾數(小數點後邊的數,小數點前邊的數必爲1);
符號正爲0,負爲1,指數採用餘碼方式(使得沒有指數部分沒有複數)float爲餘127碼(指數加上127),double爲餘1023碼(指數加上11023),尾數部分可以看成是正整數的存儲方式;
float - 符號佔1位,指數佔8位,尾數佔23位,double - 符號佔1位,指數佔11位,尾數佔52位。
---------------------
版權聲明:本文爲CSDN博主「半仙劉」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_16334327/article/details/80200869