做過socket的都知道網(wǎng)絡(luò)字節(jié)轉(zhuǎn)換的事情,網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)是純字節(jié)流,沒有類型信息,從低地址開始傳遞;網(wǎng)絡(luò)字節(jié)序通常為大端的,即先傳遞高字節(jié),因此和大端的本地字節(jié)存儲順序一致,和小端的則截然相反。為了數(shù)據(jù)的一致性,就要把本地的數(shù)據(jù)轉(zhuǎn)換成網(wǎng)絡(luò)上使用的格式,然后發(fā)送出去,接收的時候也是一樣的,經(jīng)過轉(zhuǎn)換然后才去使用這些數(shù)據(jù)?;镜膸旌瘮?shù)中提供了這樣的可以進行字節(jié)轉(zhuǎn)換的函數(shù),如和htons( ) htonl( ) ntohs( ) ntohl( ),這里n表示network,h表示host,htons( ) htonl( )用于本地字節(jié)向網(wǎng)絡(luò)字節(jié)轉(zhuǎn)換的場合,s表示short,即對2字節(jié)操作,l表示long即對4字節(jié)操作。同樣ntohs( )ntohl( )用于網(wǎng)絡(luò)字節(jié)向本地格式轉(zhuǎn)換的場合。隨著c99標(biāo)準(zhǔn)的推行,我們偉大的c中增加了新的類型long long int ,unsigned long long int,都是64位的,怎么辦?不轉(zhuǎn)肯定是不行,就得自己想辦法把它轉(zhuǎn)了。當(dāng)然有很多方法,我這里想使用一種類比的解決方法,看看如何舉一反三。
一、字節(jié)序定義字節(jié)序,顧名思義字節(jié)的順序,再多說兩句就是大于一個字節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序。其實大部分人在實際的開發(fā)中都很少會直接和字節(jié)序打交道。唯有在跨平臺以及網(wǎng)絡(luò)程序中字節(jié)序才是一個應(yīng)該被考慮的問題。一次Sun SPARC到Intel X86的平臺移植讓我們的程序遭遇了“字節(jié)序問題”。
在所有的介紹字節(jié)序的文章中都會提到字節(jié)序分為兩類:Big-Endian和Little-Endian。引用標(biāo)準(zhǔn)的Big-Endian和Little-Endian的定義如下: a) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。 b) Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。 c) 網(wǎng)絡(luò)字節(jié)序:TCP/IP各層協(xié)議將字節(jié)序定義為Big-Endian,因此TCP/IP協(xié)議中使用的字節(jié)序通常稱之為網(wǎng)絡(luò)字節(jié)序。
二、高/低地址與高低字節(jié)首先我們要知道我們C程序映像中內(nèi)存的空間布局情況:在《C專家編程》中或者《Unix環(huán)境高級編程》中有關(guān)于內(nèi)存空間布局情況的說明,大致如下圖: ----------------------- 最高內(nèi)存地址 0xffffffff 。。。。。。 | 棧底 . . 棧 棧頂 ----------------------- | | NULL (空洞) /|\ ----------------------- 堆 ----------------------- 未初始化的數(shù)據(jù) ---------------------- (統(tǒng)稱數(shù)據(jù)段) 初始化的數(shù)據(jù) ----------------------- 正文段(代碼段) ----------------------- 最低內(nèi)存地址 0x00000000 以上圖為例如果我們在棧上分配一個unsigned char buf[4],那么這個數(shù)組變量在棧上是如何布局的呢?看下圖: 棧底(高地址) ---------- buf[3] buf[2] buf[1] buf[0] ---------- 棧頂(低地址)
現(xiàn)在我們弄清了高低地址,接著我來弄清高/低字節(jié),如果我們有一個32位無符號整型0x12345678(呵呵,恰好是把上面的那4個字節(jié)buf看成一個整型),那么高位是什么,低位又是什么呢?其實很簡單。在十進制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進制也是如此。就拿0x12345678來說,從高位到低位的字節(jié)依次是0x12、0x34、0x56和0x78。 高低地址和高低字節(jié)都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,并用圖示說明兩種字節(jié)序: 以unsigned int value = 0x12345678為例,分別看看在兩種字節(jié)序下其存儲情況,我們可以用unsigned char buf[4]來表示value: Big-Endian: 低地址存放高位,如下圖: 棧底(高地址) --------------- buf[3] (0x78) -- 低位 buf[2] (0x56) buf[1] (0x34) buf[0] (0x12) -- 高位 --------------- 棧頂(低地址) Little-Endian: 低地址存放低位,如下圖: 棧底(高地址) --------------- buf[3] (0x12) -- 高位 buf[2] (0x34) buf[1] (0x56) buf[0] (0x78) -- 低位 --------------- 棧頂(低地址)
在現(xiàn)有的平臺上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。
三、網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換假設(shè)對于little endian的IA-32架構(gòu)上面的Linux,首先考慮網(wǎng)絡(luò)字節(jié)轉(zhuǎn)換的結(jié)果與原來有什么不同,如 int a = 0x12345678,b = htnl(a),那么就應(yīng)該是0x78563412。如果是 short c = 0x1234,short d = 0x5678,e = htons(c),f = htons(d),這樣e=0x3412,f=0x7856,如果能把e和f調(diào)換一下組合放在一起,不就是一個整型a(a=0x12345678)轉(zhuǎn)換之后的值么?實驗的代碼如下: #include <stdio.h> struct ST{ short val1; short val2; }; union U{ int val; struct ST st; };
int main(void) { int a = 0; union U u1, u2;
a = 0x12345678; u1.val = a; printf("u1.val is 0x%x\n", u1.val); printf("val1 is 0x%x\n", u1.st.val1); printf("val2 is 0x%x\n", u1.st.val2); printf("after first convert is: 0x%x\n", htonl(u1.val)); u2.st.val2 = htons(u1.st.val1); u2.st.val1 = htons(u1.st.val2); printf("after second convert is: 0x%x\n", u2.val); return 0; } 輸出結(jié)果: u1.val is 0x12345678 val1 is 0x5678 val2 is 0x1234 after first convert is: 0x78563412 after second convert is: 0x78563412
按照這種想法我們實現(xiàn)long long int(64bit)類型,把它分割成兩個int(32bit),然后分別使用htonl(),分別轉(zhuǎn)換,然后再將兩種int交換順序重新組合,即實現(xiàn)了整個64位的八個字節(jié)的翻轉(zhuǎn)。 代碼如下: #include <stdio.h> struct ST{ int val1; int val2; }; union test { long long int val; struct ST st; };
int main(void) { long long int a; union test u1, u2;
a = 0x7654321087654321LL; u1.val = a; u2.st.val2 = htonl(u1.st.val1); u2.st.val1 = htonl(u1.st.val2); printf("val1 is 0x%x\n", u2.st.val1); printf("val2 is 0x%x\n", u2.st.val2); printf("u1.val is : 0x%llx\n", u1.val); printf("after convert is : 0x%llx\n", u2.val);
return 0; } 執(zhí)行結(jié)果: val1 is 0x10325476 val2 is 0x21436587 u1.val is : 0x7654321087654321 after convert is : 0x2143658710325476
另外注意long long int 最大值是0x7fffffffffffffff,即7后面15個f(2的63次方減1) unsigned long long int 最大值是0xffffffffffffffff,16個f(2的64次方減1)。程序中long long int可以簡寫為 long long,但是記住這是簡寫,就像long是long int的簡寫。
想看數(shù)據(jù)在內(nèi)存中如何存儲的,就用gdb吧!使用gdb中 x命令,如 x /xb &a表示要察看存儲在變量a中的前一個字節(jié)(byte)中的數(shù)據(jù)(16進制)。x /xw &a 就是要察看變量a中前4個字節(jié)(word)數(shù)據(jù)(16進制)。x /xg &a 察看a開始8個字節(jié)的數(shù)據(jù)。
|