天天看點

大小端模式詳解

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記憶體中的存放方式則為:

32bit寬的數0x12345678在Little-endian模式CPU記憶體中的存放方式(假設從位址0x4000開始存放)為:

0x4002 0x4003
0x78 0x56

聯合體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
0x03

小端:較高的有效位元組存放在較高的的存儲器位址,較低的有效位元組存放在較低的存儲器位址。 大端:較高的有效位元組存放在較低的存儲器位址,較低的有效位元組存放在較高的存儲器位址。 如果将一個16位的整數0x1234存放到一個短整型變量(short)中。這個短整型變量在記憶體中的存儲在大小端模式由下表所示。

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

有的處理器系統采用了小端方式進行資料存放,如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)        return TRUE;    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報頭的發送、檢驗、接收源代碼。

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

What Doesn't Kill Me Makes Me Stronger