大端和小端


  在計算機中是以字節為單位,每個地址對應一個字節,一個字節8bit。在C中,除了8bit的char以外,還有16bit的short,32位的int,64位long,當然具體要由編譯器決定,可以通過sizeof來獲取不同類型在內存中占用的字節數。在計算機系統中,當物理單位的長度大於1個字節時,就要區分字節順序。常見的字節順序有兩種:Big Endian(High-byte first)和Litter Endian(Low-byte first),當然還有其他字節順序,但不常見,例如Middle Endian。

一、最高有效位、最低有效位

  要理解Big Endian和Little Endian,首先要搞清楚MSB和LSB。

  1、MSB(Most Significant Bit)最高有效位

    在一個n位二進制數字中n-1位,也就是最左邊的位。

  2、LSB(Least Significant Bit)最低有效位

    指最右邊的位。

  例如:一個int類型的整型123456789

    二進制表達方式:0000 0111 0101 1011 1100 1101 0001 0101(從右向左,每4bit對齊,最左邊(高位)不夠用0補齊)

    十六進制表達方式:0 7 5 B C D 1 5

    按照上述關於MSB和LSB的意思,在二進制表達方式中,bit從0開始,從右向左,bit0為最低有效位,而bit23為最高有效位。而我們一般稱左邊的0x07為高位字節,0x15為低位字節。

    再通俗一點解釋就是:8421碼的,8這端為高位,1這端為低位,相應的字節則分別稱為高位字節和低位字節。

二、內存地址

  在內存中,多字節對象都是被存儲為連續的字節序列。例如在C語言中,一個類型為int的變量n,如果其存儲的首個字節的地址為0x1000,那么剩余3個字節的地址將存儲在0x1001~0x1003。總之,不管具體字節順序是以什么方式排列,內存地址的分配一般是從小到大的增長。我們常把0x1000稱為低地址端,把0x1003稱為高地址端。

三、大端和小端

  搞清楚MSB、LSB、高位字節、低位字節、內存地址之后,再理解大端和小端,就相當容易了,先看看概念:

    小端Little Endian:低字節存放在低地址,低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。

    大端Big Endian:高字節存放在低地址,即高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

  以二節中的例子int類型整數123456789為例:

    小端在內存中排列:0x15 0xCD 0x5B 0x07 (低位在前)

    大端在內存中排列:0x07 0x5B 0xCD 0x15 (高位在前)

  從例子中可以看出小端比較符合人的思維,而大端則看上去非常直觀。

  注:

    1、例子中是假設編譯器支持int為32位的前提下,如果是16位,那大端的排列則為:0xCD 0x15 0x07 0x5B。

    2、大小端一般是由CPU架構決定,常見的Intel、AMD的CPU使用的是小端字節序,而PowerPC使用的是大端字節序,有些ARM處理器還可以選擇用大端還是小端模式,具體請自行查閱。

    3、c#中,字節序跟編譯平台所在的CPU相關,例如在Intel x86 CPU架構的windows平台中,c#采用的小端序。而Java由於其JVM屏蔽了不同CPU架構導致的字節序差異,所以默認采用大端字節序。所以,大小端模式是由CPU決定,而編譯器又可能會改變這種模式。

字節序 內存地址 int(16bit) int(32bit) 特點
小端 0x1001,0x1002,0x1003,0x1004 0x15 0xCD 0x5B 0x07 0x15 0xCD 0x5B 0x07 低地址端存儲低位字節,低位在前
大端 0x1001,0x1002,0x1003,0x1004 0xCD 0x15 0x07 0x5B 0x07 0x5B 0xCD 0x15 低地址端存儲高位字節,高位在前

四、網絡字節序和主機字節序

  網絡字節序(Network Order):TCP/IP各層協議將字節序定義為Big Endian,因此TCP/IP協議中使用的字節序通常稱之為網絡字節序。

  主機字節序(Host Order):整數在內存中保存的順序,它遵循Little Endian規則(不一定,要看主機的CPU架構)。所以當兩台主機之間要通過TCP/IP協議進行通信的時候就需要調用相應的函數進行主機序列(Little Endian)和網絡序(Big Endian)的轉換。

  如果是做跨平台開發時,雙方需要協商好字節序,然后根據程序運行的環境,確定是否需要字節序轉換。

例如約定的通訊字節序位Big Endian,默認的windows采用的Little Endian,那收到數據后就需要做轉換操作。

五、C#位操作符

  這里簡單記錄一下C#的位操作符,方便以后自己查閱,也方便理解后面的講解。

  1、按位與&

    1&0為0;0&0為0;1&1為1。

  2、按位或|

    1|0為1;0|0為0;1|1為1。

  3、按位取反~

    ~1為0;~0為1。

  4、按位異或^

    1^1為0;0^0為0;1^0為1。相等得0,相異等1。

  5、左移<<

    位左移運算,將整個數向左移若干位,左移后空出的部分用0補齊。

  6、右移>>

    位右移運算,將整個數向右移若干位,右移后空出的部分用0補齊。

六、C#中關於大端和小端的轉換
  1、重復輪子

using System;

namespace Framework.NetPackage.Common
{
    /// <summary>
    /// 字節序轉換輔助類
    /// </summary>
    public static class Endian
    {
        public static short SwapInt16(this short n)
        {
            return (short)(((n & 0xff) << 8) | ((n >> 8) & 0xff));
        }

        public static ushort SwapUInt16(this ushort n)
        {
            return (ushort)(((n & 0xff) << 8) | ((n >> 8) & 0xff));
        }

        public static int SwapInt32(this int n)
        {
            return (int)(((SwapInt16((short)n) & 0xffff) << 0x10) |
                          (SwapInt16((short)(n >> 0x10)) & 0xffff));
        }

        public static uint SwapUInt32(this uint n)
        {
            return (uint)(((SwapUInt16((ushort)n) & 0xffff) << 0x10) |
                           (SwapUInt16((ushort)(n >> 0x10)) & 0xffff));
        }

        public static long SwapInt64(this long n)
        {
            return (long)(((SwapInt32((int)n) & 0xffffffffL) << 0x20) |
                           (SwapInt32((int)(n >> 0x20)) & 0xffffffffL));
        }

        public static ulong SwapUInt64(this ulong n)
        {
            return (ulong)(((SwapUInt32((uint)n) & 0xffffffffL) << 0x20) |
                            (SwapUInt32((uint)(n >> 0x20)) & 0xffffffffL));
        }
    }
}

  2、BCL庫支持的函數

    System.Net.IPAddress.HostToNetworkOrder、System.Net.IPAddress.NetworkToHostOrder,這兩個函數的內部實現和上面重復輪子原理一模一樣。

七、關於負數

  在計算機中,負數以其絕對值的補碼形式表示,不明白可以查閱九中貼出的相關資源。關於負數的字節序跟一般整數的字節序處理沒有任何區別。

八、關於漢字編碼以及與字節序的關系

  1、對於gb2312、gbk、gb18030、big5,其編碼某個漢字產生的字節順序,由其編碼方案本身決定,不受CPU字節序的影響。其實這幾種編碼的字節序和大端模式的順序是一致的。

  在使用GB2312的程序通常采用EUC儲存方法,以便兼容於ASCII。瀏覽器編碼表上的“GB2312”,通常都是指“EUC-CN”表示法。
每個漢字及符號以兩個字節來表示。第一個字節稱為“高位字節”,第二個字節稱為“低位字節”。
  “高位字節”使用了0xA1-0xF7(把01-87區的區號加上0xA0),“低位字節”使用了0xA1-0xFE(把01-94加上0xA0)。

  由於一級漢字從16區起始,漢字區的“高位字節”的范圍是0xB0-0xF7,“低位字節”的范圍是0xA1-0xFE,占用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。
  例如“啊”字在大多數程序中,會以兩個字節,0xB0(第一個字節)0xA1(第二個字節)儲存。(與區位碼對比:0xB0=0xA0+16,0xA1=0xA0+1)。

  2、UTF-8

      UTF-8和gb系列編碼一樣,其編碼某個漢字產生的字節順序,由其編碼方案決定,不受CPU字節序的影響。無論一個漢字有多少個字節,它的字節序與編碼順序保持一致。

例如漢字”嚴”利用utf8編碼過程:

1、已知“嚴”的unicode編碼是4E25(100111000100101),根據utf8規則可以得知其utf8編碼需要三個字節。

  即格式是“1110xxxx 10xxxxxx 10xxxxxx”

  第一個字節前三位表示了字符“嚴”被編碼成utf8后的編碼長度,有多長,則從左開始填多少個1,如果只有1個字節,則第一個位為0。

  對於編碼后大於1個字節的符號,第一個字節的第四位為0,其他字節前兩位均要求為10。

2、從”嚴“的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了“嚴”的utf8編碼為“11100100 10111000 10100101”,轉換成十六進制就是E4B8A5。

編碼示例過程參考的原文:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

從上述過程可以看到,utf8的字節序已經由其編碼方案決定,不受CPU字節序影響。

  3、Unicode

    Unicode只是一個符號集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。所以他沒有要求如何存儲編碼后的字節,也就受CPU字節序的影響。

    Unicode的具體實現包括UTF-16、UTF-32(當然也包括UTF-8,但由於其編碼方式和編碼后的字節序與其他Unicode編碼實現有較大區別,所以單獨拿出來講解的)。

  4、總結

    1、網絡通訊

      在實際的網絡通訊中,網絡協議例如TCP是規定網絡字節序(Network Order)是大端。而針對漢字具體使用什么編碼,通訊雙方要么提前約定好,要么就需要在數據包中標識好漢字具體使用的編碼。

      如果在網絡通訊中,涉及例如UTF16這樣區分大小端的編碼,除非按網絡協議要求采用大端模式是,否則也要事先約定好,或者在數據包中標識好使用的字節序模式。

    2、文件

      文件的也會存儲漢字,當然也要進行編碼。如果采用UTF-16這樣的有字節序模式區分的編碼,編碼規則要求可以在文件頭部的BOM(Byte Order Mark)來標記。如果沒有標記,除非事先知道字節序的模式,否則只能大小端都試一遍。

 

  Unicode規范中推薦的標記字節順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:

  在Unicode編碼中有一個叫做”ZERO WIDTH NO-BREAK SPACE”的字符,它的編碼是FEFF。而FEFF在Unicode中是不存在的字符,所以不應該出現在實際傳輸中。UCS(Unicode的學名)規范建議我們在傳輸字節流前,先傳輸字符“ZERO WIDTH NO-BREAK SPACE”。

  這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符“ZERO WIDTH NO-BREAK SPACE”又被稱作BOM。

  UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符“ZERO WIDTH NO-BREAK SPACE”的UTF-8編碼是EF BB BF。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。

九、參考資源

  http://baike.baidu.com/view/1922338.htm

  http://www.cppblog.com/izualzhy/archive/2011/10/20/158784.html

  http://www.cnblogs.com/junsky/archive/2009/08/06/1540727.html(負數的二進制表示方法)

  http://www.cnblogs.com/augellis/archive/2009/09/29/1576501.html (sizeof)

  http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html(漢字編碼)

  http://djt.qq.com/article/view/658?ADTAG=email.InnerAD.weekly.20130902(漢字編碼)


免責聲明!

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



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