(轉)大小端模式詳解


int i=1;  

char *p=(char *)&i;  

 if(*p==1)              

 printf("1");     

else           

printf("2");

          大小端存儲問題,如果小端方式中(i占至少兩個字節的長度)則i所分配的內存最小地址那個字節中就存着1,其他字節是0.大端的話則1在i的最高地址字節處存放,char是一個字節,所以強制將char型量p指向i則p指向的一定是i的最低地址,那么就可以判斷p中的值是不是1來確定是不是小端。

 

請寫一個C函數,若處理器是Big_endian的,則返回0;若是Little_endian的,則返回1
解答:
int checkCPU( )
{
    {
           union w
           { 
                  int a;
                  char b;
           } c;
           c.a = 1;
           return(c.b ==1);
    }
}
剖析:
嵌入式系統開發者應該對Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU對操作數的存放方式是從低字節到高字節,而Big-endian模式對操作數的存放方式是從高字節到低字節。例如,16bit寬的數0x1234在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)為:
內存地址 0x4000 0x4001
存放內容 0x34 0x12
而在Big-endian模式CPU內存中的存放方式則為:
內存地址 0x4000 0x4001
存放內容 0x12 0x34
32bit寬的數0x12345678在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)為:
內存地址 0x4000 0x4001 0x4002 0x4003
存放內容 0x78 0x56 0x34 0x12
而在Big-endian模式CPU內存中的存放方式則為:
內存地址 0x4000 0x4001 0x4002 0x4003
存放內容 0x12 0x34 0x56 0x78
聯合體union的存放順序是所有成員都從低地址開始存放,面試者的解答利用該特性,輕松地獲得了CPU對內存采用Little-endian還是Big-endian模式讀寫。如果誰能當場給出這個解答,那簡直就是一個天才的程序員。
 
 
補充:
所謂的大端模式,是指數據的低位(就是權值較小的后面那幾位)保存在內存的高地址中,而數據的高位,保存在內存的低地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;   
所謂的小端模式,是指數據的低位保存在內存的低地址中,而數據的高位保存在內存的高地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低,和我們的邏輯方法一致。   
為什么會有大小端模式之分呢?這是因為在計算機系統中,我們是以字節為單位的,每個地址單元都對應着一個字節,一個字節為 8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於 8位的處理器,例如16位或者32位的處理器,由於寄存器寬度大於一個字節,那么必然存在着一個如果將多個字節安排的問題。因此就導致了大端存儲模式和小 端存儲模式。
例如一個16bit的short型x,在內存中的地址為0x0010,x的值為0x1122,那么0x11為高字節,0x22為低字節。
對於大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。
小端模式,剛好相反。我們常用的X86結構是小端模 式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬件來選擇是大端模式還是小端模式。
 
下面這段代碼可以用來測試一下你的編譯器是大端模式還是小端模式:
 
short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址單元
x1=((char*)&x)[1]; //高地址單元 若x0=0x11,則是大端; 若x0=0x22,則是小端......
上面的程序還可以看出,數據尋址時,用的是低位字節的地址。
 
-----------------------------------------------------------------------------------------------

什么時候要進行大小端字節序的轉換?  

short 或者 long的數據在進行通信的時候最好養成:

1、發送的時候使用:htons(l)

2、接受的時候使用:ntohs(l)  而不要理會兩邊的通信是否需要這么做~~  當然了一般我都不用int型的數據通信,從來都是字符串通信,發送方利用sprintf組織,接收方利用atoi進行轉換~~

-----------------------------------------------------------------------------------------------

 

端模式(Endian)的這個詞出自Jonathan Swift書寫的《格列佛游記》。這本書根據將雞蛋敲開的方法不同將所有的人分為兩類,從圓頭開始將雞蛋敲開的人被歸為Big Endian,從尖頭開始將雞蛋敲開的人被歸為Littile Endian(這句話最為形象)。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。在計算機業Big Endian和Little Endian也幾乎引起一場戰爭。在計算機業界,Endian表示數據在存儲器中的存放順序。下文舉例說明在計算機中大小端模式的區別。

如果將一個32位的整數0x12345678存放到一個整型變量(int)中,這個整型變量采用大端或者小端模式在內存中的存儲由下表所示。為簡單起見,本文使用OP0表示一個32位數據的最高字節MSB(Most Significant Byte),使用OP3表示一個32位數據最低字節LSB(Least Significant Byte)。

 

地址偏移

大端模式

小端模式

0x00

12(OP0)

78(OP3)

0x01

34(OP1)

56(OP2)

0x02

56(OP2)

34(OP1)

0x03

78(OP3)

12(OP0)

 

小端:較高的有效字節存放在較高的的存儲器地址,較低的有效字節存放在較低的存儲器地址。 

大端:較高的有效字節存放在較低的存儲器地址,較低的有效字節存放在較高的存儲器地址。 

如果將一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在內存中的存儲在大小端模式由下表所示。

 

地址偏移

大端模式

小端模式

0x00

12(OP0)

34(OP1)

0x01

34(OP1)

12(OP0)

 

由上表所知,采用大小模式對數據進行存放的主要區別在於在存放的字節順序,大端方式將高位存放在低地址,小端方式將高位存放在高地址。采用大端方式進行數據存放符合人類的正常思維,而采用小端方式進行數據存放利於計算機處理。到目前為止,采用大端或者小端進行數據存放,其孰優孰劣也沒有定論。

有的處理器系統采用了小端方式進行數據存放,如Intel的奔騰。有的處理器系統采用了大端方式進行數據存放,如IBM半導體和Freescale的PowerPC處理器。不僅對於處理器,一些外設的設計中也存在着使用大端或者小端進行數據存放的選擇。

因此在一個處理器系統中,有可能存在大端和小端模式同時存在的現象。這一現象為系統的軟硬件設計帶來了不小的麻煩,這要求系統設計工程師,必須深入理解大端和小端模式的差別。大端與小端模式的差別體現在一個處理器的寄存器,指令集,系統總線等各個層次中。

【用函數判斷系統是Big Endian還是Little Endian】

//如果字節序為big-endian,返回true;

//反之為   little-endian,返回false

bool IsBig_Endian()

{     

unsigned short test = 0x1234;    

if(*( (unsigned char*) &test ) == 0x12)       

 returnTRUE;   

else       

 return FALSE;

 

}//IsBig_Endian()

 

附:

大小端的分度值是 byte,即每一個byte都是按照正常順序,但是byte組裝成一個int 或者是 long等時每個byte的擺放位置不同

------------------------------------------------------------------------------------------------

眾所周知,同樣一組數據,存儲和表示的順序可以是多樣的,也就是存儲和表示格式是多樣的[1]。相同的數據,轉換成二進制之后,在不同的計算機存儲器中的內部表示也是有區別的,這會對編程產生一定的影響。本文主要探討大小端存儲模式對編程的影響及在編程時應采取的對策。

1 大小端存儲模式概述

     計算機中的存儲器(內存)由大量的存儲元構成。存儲元是存儲器的最小物理組成單位,用來存放一位二進制數0或1。把這些存儲元按相同的位數(通常是1字節8位的1, 2, 4, 8倍)划分成組,組內所有存儲元同時讀出或寫入信息,即為存儲單元[2]。每個存儲單元都有一個惟一的編號,叫做單元地址。存儲單元是CPU訪問存儲器的基本單位,CPU通過單元地址訪問(讀或寫)相 應的存儲單元。不同的計算機,存儲單元地址的編排方式有所不同。如果進行編址的最小單位是字,稱為按字編址,如圖1 (a)所示。如果編址的最小單位是字節,則稱按字節編址。因此, CPU一次訪問一個存儲單元就可能訪問若干個獨立編址的字節,圖1(b)為PDP-11機器,一個存儲單元存放2個字節,低字節用偶地址,高字節用奇地址,字地址是2的倍數,即它的低字節的地址。圖1(c)為IBM-370機器,一個存儲單元存放4個字節,字地址是4的整數倍,即它的高字節的地址(與圖1(b)所示情況相反)。圖1 存儲器的不同編址方式按字節編址是當前存儲器編址方式的主流,因為數據處理的最小單位是字節。從軟件角度看,存儲器就是一個很大的字節數組。通常,CPU和編譯器使用不同的格式來編碼數據,如不同長度的整數和浮點數,從而支持多種數據類型。程 序中,先定義這些類型的變量,到內存中分配字節空間,當CPU讀寫這些變量,即訪問內存時,往往依據數據類型的不同,一次可讀寫若干個字節。對於多於一個字節的數據(通常為字節長度的2N倍,N=1, 2, 3),在存儲器中有兩種存儲方式,即定義半字、字、雙字與字節之間的對應關系的兩種映射機制[3]。一種是數據的低字節部分存放在內存低地址處,高字節部分存放在內存高 地址處,稱為小端字節順序存儲法,又稱小端存儲模式;另一種是高字節數據存放在低地址處,低字節數據存放在高地址處,稱為大端字節順序存儲法,又稱大端存儲模。不難看出,圖1(b)所示為小端模式,而圖1(c)即為大端模式。支持大端存儲模式還是小端存儲模式,並不存在技術原因,只是涉及到處理器廠商的立場和習慣。

2 存儲模式不同而導致的問題

    對於大多數程序員而言,機器的字節存儲順序是完全不可見的,無論哪一種存儲模式的處理器編譯出的程序都會得到相同的結果。即對於同一段源代碼,單獨在小端機器上編譯運行,其結果與單獨在大端機器上編譯運行的結果一樣,盡管同一個數據在大小端格式下的內存表示有區別,但在應用程序員和用戶眼里,參與算術邏輯運算、寫入讀出的數據卻是無差別的。不過,有些情況下,字節順序會成為問題。

2. 1 UNIX問題———程序可移植性問題

    最早在將UNIX操作系統的早期版本從PDP-11移植到IBM機器上時,數據“UNIX”在16位字長的小端格式的PDP-11上被表示為2個字4個字節,當被移植到大端存儲模式的IBM機器上時,就會變成“NUXI”,這被稱為UNIX問題。因此,當在不同存儲順序的處理器間進行程序移植時,需要特別注意存儲模式的影響。

2. 2 閱讀、解釋、共享二進制數據的問題

    對同一段可執行程序進行反匯編,使用反匯編器閱讀機器級二進制代碼時,在不同存儲模式的處理器上會看到不同的結果;在解釋、共享以二進制格式存儲的數據和使用掩碼時,不同的存儲順序會得出不同的結果,例如,某32位小端機器,存儲某常量0xE48623A0到某二進制文件,在大端模式下讀出的是0xA02386E4,若把該數據看做IPv4地址,則存儲並使用合適的掩碼進行按位與運算也得考慮到存儲模式的影響。

2. 3 網絡數據傳輸的問題

    當不同存儲模式的處理器之間通過網絡傳送二進制數據時,會產生高低字節翻轉現象,例如,從32位小端機器,發送某常量0x01234567,發送和接收緩沖區內(地址從低到高)的字節順序為0x67、0x45、0x23、0x01,接收該數據的對方機器為 32位大端模式,則讀出的數值是0x67452301,相比原值,高低字節互換了位置。

3 編程時可采取的對策

    編程時,應考慮該應用是否與存儲模式相關,若需要在不同存儲模式的處理器之間移植程序、共享數據、網絡通信,可以嘗試以下對策。若是程序移植,則在程序中添加如下的預編譯條件,針對不同存儲模式,分別處理,然后在頭文件中定義當前處理器和編譯器支持的存儲模式,如: #define Little-End #ifdefBig_End …… #endif #ifdefLittle_End …… #endif 若是信息共享,則有兩種解決方法: (1)以單一存儲順序共享數據,只需解釋一種格式,所以解碼簡單; (2)允許各主機以不同的存儲順序共享數據,但需標記出哪種模式,無需對數據的原順序進行轉化,所以編碼容易,當發送方編碼和接收方解碼采用同一種存儲模式時,無需變換字節順序,可以提高通信效率。

若是網絡通信,可以參照並遵循TCP/IP協議定義的標准的網絡字節順序,由發送方處理器先在其內部將發送的數據轉換成網絡標准,而接收方處理器再將網絡標准轉換為它的內部表示。Berkeley應用程序接口定義了一套轉換函數,如:函數htonl和htons分別將32位長整型和16位短整型數值從主機字節順序轉化成網絡字節順序;而函數ntohl和ntohs則將網絡字節順序轉化為主 機字節順序。

4 案例分析

    ZLG/IP可運行於大端機器,也可運行於小端機器,涉及程序的可移植性; ZLG/IP是嵌入式網絡通信協議,必然涉及多個主機間的數據共享和網絡傳輸;由大小端存儲模式不同而導致的問題,ZLG/IP都會遇到,它是如何解決的呢?限於篇幅,這里僅摘錄IP. c中IP報頭的發送、校驗、接收函數的部分源代碼。存儲模式不同不會影響IP報頭的字節變量成員的讀寫,也不會影響IP地址的讀寫(IP地址按大端順序采用字節數組存儲),所以,只選取IP報頭的16位半字變量成員作案例分析。

4. 1 發送函數

    原版本定義了一個局部字節數組,無論在哪種存儲模式下編譯運行,均將半字變量拆分成兩個字節,高字節寫入低地址,低字節寫入高地址,即直接按大端順序寫入字節數組,保證發送和接收緩沖區的內存表示的一致性,即網絡傳輸的IP報頭的一致性。

eip e_ip;

uint8 IpHeadUint8[20];

…… e_ip. TotalLen=(*TxdData). length+20; IpHeadUint8[2]=(e_ip.TotalLen&0xff00)>>8;

IpHeadUint8[3]=e_ip.TotalLen&0x00f;

f …… e_ip.Crc=CreateIpHeadCrc(IpHeadUint8);

IpHeadUint8[10]=(e_ip. Crc&0xff00)>>8;

IpHeadUint8[11]=e_ip.Crc&0x00f;

f …… TxdIpData.DAPTR=IpHeadUnit8;

Send_Ip_To_LLC (&TxdIpData, e_ip. DestId, num);

     筆者的做法是利用共用體類型union ip-rc實現,可以節省局部字節數組空間,大小端情況區別對待,大端模式下,直接寫入原值;小端模式下,先寫入原值,再對該變量成員作高低字節轉換,即最終寫入的值不同(與原值字節順序相反),內存表示卻變的相同了(原值的大端順序)。

union ip_rc {eip e_ip; struct {uint16 wordbuf[10]; } words; };

union ip_rc IpHead; …… IpHead. e_ip. TotalLen=(*TxdData). length+20;

#ifdefLittle_End IpHead. e_ip. TotalLen=swap_ int16( IpHead e_ ip. To- talLen);

#endif …… IpHead. e_ip. Crc=CreateIpHeadCrc_1 ( IpHead. words. wordbuf);

#ifdefLittle_End IpHead. e_ip. Crc=swap_int16(IpHead. e_ip. Crc);

#endif …… TxdIpData. DAPTR=(uint8* ) & IpHead. e_ip; Send_Ip_To_LLC (& TxdIpData, IpHead. e_ip. DestId, num);

4. 2 IP報頭校驗和計算函數

    不論哪種存儲模式下,為了保證校驗和結果的惟一性,原版本對傳遞過來的8位字節數組(內存表示默認為大端順序)強制按半字16位大端順序讀出及求和。

union w Crc: //類型union w在ip. h中定義,存儲模式不同,定義也不同

Crc. dwords=0;

for ( i=0; i<10; i++) Crc. dwords=Crc. dwords+ ((uint 32) Ip[2* i]<<8) + (uint32)Ip[2* i+1];

筆者實現的校驗和計算函數更簡潔更通用,形參16位半字數組是確定不變的大端字節順序,大端模式下,讀出的IpH[ i]就是原值,直接累加即可;小端模式下,讀出IpH[ i]后必須轉換高低字節,才是當初寫入的原值,然后再累加。

uint32 temp=0;

for ( i=0; i<10, i++)

{

#ifdefBig_End temp=temp+(uint32) IpH[ i];

#endif

#ifdefLittle_End

temp=temp + (uint32) (swap_int16 (IpH[ i]));

#endif }

4. 3 接收函數

     原版本中,傳遞過來的IP報頭是大端順序字節數組中的數據,在大端模式下讀出時,就是正確的原值,在小端環境下編譯運行時,內存表示是大端順序,卻按小端順序讀出,結果值與原值高低字節順序相反,因此,要將讀出的結果進行高低字節互換,才能得到正確的原值。

#ifdefBig_End PackedLength=((eip* )RecData)->TotalLen;

#endif #ifdefLittle_End PackedLength=((eip* )RecData)->TotalLen;

Ltemp=PackedLength&0x00f;

f PackedLength=(PackedLength&0xff00)>>8;

PackedLength=PackedLength+(Ltemp<<8);

#endif

而筆者精減了代碼,讀操作是無論哪種存儲模式下都存在的,可以統一出來,之后針對小端模式的特殊情況,進行高低字節轉換,變回原值。

PackedLength=((eip* )RecData)->TotalLen;

#ifdefLittle_End PackedLength=swap_int16 (PackedLength));

#endif

通過閱讀源碼得知, ZLG/IP約定:無論在哪種存儲模式的機器上編譯運行, IP報頭在發送和接收緩沖區中的字節順序表示為大端字節順序。

這樣的約定有兩個好處:

(1)保證了網絡通信的數據包報頭的字節順序一致,形成網絡字節順序的標准,當在通信雙方的不同的存儲模式下讀出時,由終端計算機系統自己轉換;

(2)方便了報頭校驗和的計算,不論大小端存儲模式,一致按大端 順序讀出,保證了校驗和的惟一性。

5 結語

   由上述分析可見,本文所寫代碼可以節省局部數組空間,代碼量更少,函數模塊更通用。因此,可以進一步精簡ZLG/IP中ICP報頭、UDP報頭的發送、檢驗、接收源代碼。

---------------------------------------------

什么時候要進行大小端字節序的轉換?  

short 或者 long的數據在進行通信的時候最好養成:  1、發送的時候使用:htons(l)  2、接受的時候使用:ntohs(l)  而不要理會兩邊的通信是否需要這么做~~  當然了一般我都不用int型的數據通信,從來都是字符串通信,發送方利用sprintf組織,接收方利用atoi進行轉換~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM