日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

大端与小端详细介绍

發布時間:2024/4/13 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 大端与小端详细介绍 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、什么是大端和小端
所謂的大端模式,就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

所謂的小端模式,就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。

簡單來說:大端——高尾端,小端——低尾端

舉個例子,比如數字 0x12 34 56 78在內存中的表示形式為:

1)大端模式:

低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:

低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12

可見,大端模式和字符串的存儲模式類似。

3)下面是兩個具體例子:

16bit寬的數0x1234在Little-endian模式(以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)為:

內存地址 小端模式存放內容 大端模式存放內容
0x4000 0x34 0x12
0x4001 0x12 0x34
32bit寬的數0x12345678在Little-endian模式以及Big-endian模式)CPU內存中的存放方式(假設從地址0x4000開始存放)為:

內存地址 小端模式存放內容 大端模式存放內容
0x4000 0x78 0x12
0x4001 0x56 0x34
0x4002 0x34 0x56
0x4003 0x12 0x78

4)大端小端沒有誰優誰劣,各自優勢便是對方劣勢:

小端模式 :強制轉換數據不需要調整字節內容,1、2、4字節的存儲方式一樣。
大端模式 :符號位的判定固定為第一個字節,容易判斷正負。

二、數組在大端小端情況下的存儲:
  以unsigned int value = 0x12345678為例,分別看看在兩種字節序下其存儲情況,我們可以用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) – 低位
--------------
低地址

三、為什么會有大小端模式之分呢?
這是因為在計算機中,我們是以字節為單位的,每個地址單元都對應著一個字節,一個字節為 8 bit。但是在C 語言中除了 8 bit 的char之外,還有 16 bit 的 short型,32bit的long型(要看具體的編譯器),另外,對于位數大于8位的處理器,例如16位或者32位的處理器,由于寄存器寬度大于一個字節,那么必然存在著一個如果將多個字節安排的問題。因此就導致了大端存儲模式和小端存儲模式。例如一個16bit的short型 x ,在內存中的地址為 0x0010,x 的值為0x1122,那么0x11位高字節,0x22位低字節。對于大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反。我們常用的X86結構是小端模式,而KEIL C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬件來選擇是大端模式還是小端模式。

四、如何判斷機器的字節序 (重點)
一般都是通過 union 來測試的,下面這段代碼可以用來測試一下你的編譯器是大端模式還是小端模式:

#include <stdio.h>
int main (void)
{
union
{
short i;
char a[2];
}u;
u.a[0] = 0x11;
u.a[1] = 0x22;
printf (“0x%x\n”, u.i); //0x2211 為小端 0x1122 為大端
return 0;
}
輸出結果:
0x2211
union 型數據所占的空間等于其最大的成員所占的空間。對 union 型的成員的存取都是相對于該聯合體基地址的偏移量為 0 處開始,也就是聯合體的訪問不論對哪個變量的存取都是從 union 的首地址位置開始。

聯合是一個在同一個存儲空間里存儲不同類型數據的數據類型。這些存儲區的地址都是一樣的,聯合里不同存儲區的內存是重疊的,修改了任何一個其他的會受影響。

參看:C語言再學習 – 結構和其他數據形式

  • 共用體聲明和共用體變量定義
    共用體(參考“共用體”百科詞條)是一種特殊形式的變量,使用關鍵字union來定義
    共用體(有些人也叫"聯合")聲明和共用體變量定義與結構體十分相似。其形式為:
    union 共用體名{
    數據類型 成員名;
    數據類型 成員名;

    } 變量名;
  • 參看:

    共用體表示幾個變量共用一個內存位置,在不同的時間保存不同的數據類型和不同長度的變量。在union中,所有的共用體成員共用一個空間,并且同一時間只能儲存其中一個成員變量的值。

    下例表示聲明一個共用體foo:

    union foo{/“共用”類型“FOO”/
    int i; /“整數”類型“i”/
    char c; /“字符”類型“C”/
    double k; /“雙”精度類型“K”/
    };
    再用已聲明的共用體可定義共用體變量。例如,用上面說明的共用體定義一個名為bar的共用體變量, 可寫成:
    union foo bar;
    在共用體變量bar中, 整型變量 i 和字符變量 c 共用同一內存位置。

    當一個共用體被聲明時, 編譯程序自動地產生一個變量, 其長度為聯合中最大的變量長度的整數倍。以上例而言,最大長度是double數據類型,所以foo的內存空間就是double型的長度。

    union foo/“共用”類型“FOO”/
    {
    char s[10]; /“字符”類型的數組“S”下面有“10”個元素/
    int i; /“整數”類型i/
    };
    在這個union中,foo的內存空間的長度為12,是int型的3倍,而并不是數組的長度10。若把int改為double,則foo的內存空間為16,是double型的兩倍。

  • 共用體和結構體的區別
    1)共用體和結構體都是由多個不同的數據類型成員組成, 但在任何同一時刻, 共用體只存放了一個被選中的成員, 而結構體的所有成員都存在。
    2.)對于共用體的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對于結構體的不同成員賦值是互不影響的。
  • 總結:

    恍然大悟,union 聯合之前還是沒有理解透。一開始不太理解,為什么給 a[0]、a[1] 賦值,i 沒有定義啊,為什么會有值呢,或者值為什么不是隨機數呢?現在明白了,我們為什么用 union 聯合來測試大小端,在聯合變量 u 中, 短整型變量 i 和字符數組 a 共用同一內存位置。給 a[0]、a[1] 賦值后,i 也是從同一內存地址讀值的。

    知道這層關系后,那么通過強制類型轉換,判斷其實存儲位置,也可以測試大小端了:

    #include <stdio.h>
    int main (void)
    {
    short i = 0x1122;
    char a = (char)(&i);
    printf (“0x%x\n”, *(a + 0)); //大端為 0x11 小端為 0x22
    printf (“0x%x\n”, *(a + 1));
    return 0;
    }
    輸出結果:
    0x22
    0x11

    說明:上面兩個例子,可以通過 if 語句來判斷大小端,這里只是介紹方法。

    五、常見的字節序
    一般操作系統都是小端,而通訊協議是大端的。

    1)常見CPU的字節序

    Big Endian : PowerPC、IBM、Sun
    Little Endian : x86、DEC
    ARM既可以工作在大端模式,也可以工作在小端模式。

    2)常見文件的字節序

    Adobe PS – Big Endian
    BMP – Little Endian
    DXF(AutoCAD) – Variable
    GIF – Little Endian
    JPEG – Big Endian
    MacPaint – Big Endian
    RTF – Little Endian

    另外,Java和所有的網絡通訊協議都是使用Big-Endian的編碼。

    六、如何進行大小端轉換(重點)
    第一種方法:位操作

    #include<stdio.h>

    typedef unsigned int uint_32 ;
    typedef unsigned short uint_16 ;

    //16位
    #define BSWAP_16(x)
    (uint_16)((((uint_16)(x) & 0x00ff) << 8) |
    (((uint_16)(x) & 0xff00) >> 8)
    )

    //32位
    #define BSWAP_32(x)
    (uint_32)((((uint_32)(x) & 0xff000000) >> 24) |
    (((uint_32)(x) & 0x00ff0000) >> 8) |
    (((uint_32)(x) & 0x0000ff00) << 8) |
    (((uint_32)(x) & 0x000000ff) << 24)
    )

    //無符號整型16位
    uint_16 bswap_16(uint_16 x)
    {
    return (((uint_16)(x) & 0x00ff) << 8) |
    (((uint_16)(x) & 0xff00) >> 8) ;
    }

    //無符號整型32位
    uint_32 bswap_32(uint_32 x)
    {
    return (((uint_32)(x) & 0xff000000) >> 24) |
    (((uint_32)(x) & 0x00ff0000) >> 8) |
    (((uint_32)(x) & 0x0000ff00) << 8) | \
    (((uint_32)(x) & 0x000000ff) << 24) ;
    }
    /*
    |表示按位或運算,比如: 0x0F | 0xF0 = 0xFF
    \ 表示連接下一行,一般用于定義宏的時候,因為宏定義只有一行,而有時需要寫成多行方便查看,比如:
    #define exchange(a, b) { int c;
    c = a;
    a = b;
    b = c;}
    上面的定義等價于:
    #define exchange(a, b) {int c; c = a; a = b; b = c;} */

    int main(int argc,char *argv[])
    {
    printf("------------帶參宏-------------\n");
    printf("%#x\n",BSWAP_16(0x1234)) ;
    printf("%#x\n",BSWAP_32(0x12345678));
    printf("------------函數調用-----------\n");
    printf("%#x\n",bswap_16(0x1234)) ;
    printf("%#x\n",bswap_32(0x12345678));

    return 0 ;

    }
    輸出結果:
    ------------帶參宏-------------
    0x3412
    0x78563412
    ------------函數調用-----------
    0x3412
    0x78563412
    /* %#x是帶格式輸出, 效果為在輸出前加0x. */

    這里有個思考?上面的哪個是轉換為大端,哪個是轉為小端了呢?

    參看:STM32開發 – 進制與字符串間的轉換

    舉個例子,比如數字 0x12 34 56 78在內存中的表示形式為:

    1)大端模式:

    低地址 -----------------> 高地址

    0x12 | 0x34 | 0x56 | 0x78

    2)小端模式:

    低地址 ------------------> 高地址

    0x78 | 0x56 | 0x34 | 0x12

    則:

    轉換為大端:

    pPack[2] = (u8)((len >> 8) & 0xFF);
    pPack[3] = (u8)(len & 0xFF);

    轉為為小端:

    pPack[2] = (u8)(len & 0xFF);

    pPack[3] = (u8)((len >> 8) & 0xFF);

    第二種方法:

    從軟件的角度理解端模式

    使用 htonl, htons, ntohl, ntohs 等函數

    參看:百度百科–htonl ()函數

    參看:百度百科–htons ()函數

    查看:man htonl

    NAME
    htonl, htons, ntohl, ntohs - convert values between host and network byte order

    SYNOPSIS
    #include <arpa/inet.h>

    uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohs(uint16_t netshort);

    DESCRIPTION
    The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
    The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
    The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
    The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
    On the i386 the host byte order is Least Significant Byte first, whereas the network byte order, as used on the Internet, is Most
    Significant Byte first.
    翻譯:

    htonl() //32位無符號整型的主機字節順序到網絡字節順序的轉換(小端->>大端)
    htons() //16位無符號短整型的主機字節順序到網絡字節順序的轉換 (小端->>大端)
    ntohl() //32位無符號整型的網絡字節順序到主機字節順序的轉換 (大端->>小端)
    ntohs() //16位無符號短整型的網絡字節順序到主機字節順序的轉換 (大端->>小端)

    注,主機字節順序,X86一般多為小端(little-endian),網絡字節順序,即大端(big-endian);

    舉兩個小例子:

    //示例一
    #include <stdio.h>
    #icnlude <arpa/inet.h>
    int main (void)
    {
    union
    {
    short i;
    char a[2];
    }u;
    u.a[0] = 0x11;
    u.a[1] = 0x22;
    printf (“0x%x\n”, u.i); //0x2211 為小端 0x1122 為大端
    printf (“0x%.x\n”, htons (u.i)); //大小端轉換
    return 0;
    }
    輸出結果:
    0x2211
    0x1122
    //示例二
    #include <stdio.h>
    #include <arpa/inet.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;

    }
    輸出結果:
    u1.val is 0x12345678
    val1 is 0x5678
    val2 is 0x1234
    after first convert is: 0x78563412
    after second convert is: 0x78563412

    在對普通文件進行處理也需要考慮端模式問題。在大端模式的處理器下對文件的32,16位讀寫操作所得到的結果與小端模式的處理器不同。單純從軟件的角度理解上遠遠不能真正理解大小端模式的區別。事實上,真正的理解大小端模式的區別,必須要從系統的角度,從指令集,寄存器和數據總線上深入理解,大小端模式的區別。

    以下內容了解:
    1、從系統的角度理解端模式
    先補充兩個關鍵詞,MSB和LSB:
      MSB:MoST Significant Bit ------- 最高有效位
    LSB:Least Significant Bit ------- 最低有效位

    處理器在硬件上由于端模式問題在設計中有所不同。從系統的角度上看,端模式問題對軟件和硬件的設計帶來了不同的影響,當一個處理器系統中大小端模式同時存在時,必須要對這些不同端模式的訪問進行特殊的處理。PowerPC處理器主導網絡市場,可以說絕大多數的通信設備都使用PowerPC處理器進行協議處理和其他控制信息的處理,這也可能也是在網絡上的絕大多數協議都采用大端編址方式的原因。因此在有關網絡協議的軟件設計中,使用小端方式的處理器需要在軟件中處理端模式的轉變。而Pentium主導個人機市場,因此多數用于個人機的外設都采用小端模式,包括一些在網絡設備中使用的PCI總線,Flash等設備,這也要求在硬件設計中注意端模式的轉換。本文提到的小端外設是指這種外設中的寄存器以小端方式進行存儲,如PCI設備的配置空間,NOR FLASH中的寄存器等等。對于有些設備,如DDR顆粒,沒有以小端方式存儲的寄存器,因此從邏輯上講并不需要對端模式進行轉換。在設計中,只需要將雙方數據總線進行一一對應的互連,而不需要進行數據總線的轉換。如果從實際應用的角度說,采用小端模式的處理器需要在軟件中處理端模式的轉換,因為采用小端模式的處理器在與小端外設互連時,不需要任何轉換。而采用大端模式的處理器需要在硬件設計時處理端模式的轉換。大端模式處理器需要在寄存器,指令集,數據總線及數據總線與小端外設的連接等等多個方面進行處理,以解決與小端外設連接時的端模式轉換問題。在寄存器和數據總線的位序定義上,基于大小端模式的處理器有所不同。一個采用大端模式的32位處理器,如基于E500內核的MPC8541,將其寄存器的最高位msb(most significant bit)定義為0,最低位lsb(lease significant bit)定義為31;而小端模式的32位處理器,將其寄存器的最高位定義為31,低位地址定義為0。與此向對應,采用大端模式的32位處理器數據總線的最高位為0,最高位為31;采用小端模式的32位處理器的數據總線的最高位為31,最低位為0。 大小端模式處理器外部總線的位序也遵循著同樣的規律,根據所采用的數據總線是32位,16位和8位,大小端處理器外部總線的位序有所不同。大端模式下32位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第31位,LSB是第24~31字段。小端模式下32位總線的msb是第31位,MSB是數據總線的第31~24位,lsb是第0位,LSB是7~0字段。大端模式下16位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第15位,LSB是第8~15字段。小端模式下16位總線的msb是第15位,MSB是數據總線的第15~7位,lsb是第0位,LSB是7~0字段。大端模式下8位數據總線的msb是第0位,MSB是數據總線的第0~7的字段;而lsb是第7位,LSB是第0~7字段。小端模式下8位總線的msb是第7位,MSB是數據總線的第7~0位,lsb是第0位,LSB是7~0字段。由上分析,我們可以得知對于8位,16位和32位寬度的數據總線,采用大端模式時數據總線的msb和MSB的位置都不會發生變化,而采用小端模式時數據總線的lsb和LSB位置也不會發生變化。為此,大端模式的處理器對8位,16位和32位的內存訪問(包括外設的訪問)一般都包含第0~7字段,即MSB。小端模式的處理器對8位,16位和32位的內存訪問都包含第7~0位,小端方式的第7~0字段,即LSB。由于大小端處理器的數據總線其8位,16位和32位寬度的數據總線的定義不同,因此需要分別進行討論在系統級別上如何處理端模式轉換。在一個大端處理器系統中,需要處理大端處理器對小端外設的訪問。

    2、實際中的例子

    雖然很多時候,字節序的工作已由編譯器完成了,但是在一些小的細節上,仍然需要去仔細揣摩考慮,尤其是在以太網通訊、MODBUS通訊、軟件移植性方面。這里,舉一個MODBUS通訊的例子。在MODBUS中,數據需要組織成數據報文,該報文中的數據都是大端模式,即低地址存高位,高地址存低位。假設有一16位緩沖區m_RegMW[256],因為是在x86平臺上,所以內存中的數據為小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……

    為了方便討論,假設m_RegMW[0] = 0x3456; 在內存中為0x56、0x34。
    現要將該數據發出,如果不進行數據轉換直接發送,此時發送的數據為0x56,0x34。而Modbus是大端的,會將該數據解釋為0x5634而非原數據0x3456,此時就會發生災難性的錯誤。所以,在此之前,需要將小端數據轉換成大端的,即進行高字節和低字節的交換,此時可以調用步驟五中的函數BigtoLittle16(m_RegMW[0]),之后再進行發送才可以得到正確的數據。

    原文:https://blog.csdn.net/qq_29350001/article/details/54428265
    版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

    總結

    以上是生活随笔為你收集整理的大端与小端详细介绍的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。