大小端字节序

字节序和大小端

对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。

小端模式:(高高低低)高字节放在高地址,低字节放在低地址
大端模式:(高低低高)高字节放在低地址,低字节放在高地址

举个例子,数值 0x12345678,其中 0x12 这一端是高位字节,0x78 这一端是低位字节。
该数值的存储顺序是这样的:

image.png
image 2.png

为什么没有统一?

检查奇偶性 (小端占优)

小端序优势最明显 的,大概就是检查奇偶性,即通过查看个位数,确定某个数字是奇数还是偶数。


bg2022060112.jpeg

以123456为例,大端序从左到右排列,计算机必须一直读到最后一位的个位数6,才能确定这是偶数。
小端序是从右到左排列,个位数在第一位。所以,只要读取第一位,就能确定它是偶数。

检查正负号 (大端占优)

一个类似的场景是检查正负号,确定一个数是正数还是负数。


bg2022060113.jpg

大端序的符号位在左边第一位,小端序的符号位在右边最后一位。所以,大端序有优势,只看第一位就能知道是不是负数。

比较大小 (小端占优)

下一个操作是比较大小。现在有三个数字,需要比较大小:43662576,594,2。


bg2022060108.jpg

上图是大端序排列,因为是从左到右排列,所以三个数字在右边个位数对齐。比较大小时,计算机就不得不读取每一个数的所有位,直到个位数,再进行比较。
如果改成小端序,就是下面的排列方式。


bg2022060109.jpg

小端序是从右到左,所以三个数字在第一位对齐。计算机就不需要读取所有位,哪个数字先读不到下一位,就是最小的。比如,2这个数字就没有第二位,所以读到第二位时,就知道它是最小的。
所以,比较大小时,小端序有优势。

乘法 (小端占优)

接下来,再看乘法操作。
乘法是逐位相乘,每一轮乘法都要向前进位。


bg2022060110.jpg

上图是大端序的24165乘以3841。大端序的乘法是向左进位,也就是向左边扩展,必须等到每一轮的结果都出来(上例是四轮),再相加统一写入内存。
如果改成小端序的乘法,就不需要等待下一轮的结果,每一轮都可以直接写入内存。


bg2022060111.jpg

上图是小端序的24165乘以3841。小端序的乘法是向右进位,也就是向右边扩展,左边的边界不变。每一轮结果写入内存后,就不需要移动,后面有变化只需要改动对应的位就行了。
因此,小端序的乘法有明显优势。

任意精度整数 (小端占优)

上一个例子的从低位开始计算的特性,对于任意精度整数特别有用。任意精度整数又称大整数,可以存放任意大小的整数。
它的内部实现是把整数分成一个个较小的单位,通常是 uint32(无符号32位整数)或 uint64(无符号64位整数),按顺序组合在一起。


bg2022060115.jpg

如果是大端序,第一个 u64 就是这个整数最大的部分。运算时,一旦这个数发生变化,需要进位,后面的所有位都必须移动和改写。小端序发生进位时,往往就不需要所有位移动。
小端序的另一个好处是,如果逐字节的运算从个位数开始(比如乘法和加法),可以从左到右依次运算一个个 u64,算完上一个再读取下一个。大端序就不行,必须读取整个数以后再进行运算。

更改类型 (小端占优)

最后一个例子是,C 语言有一种 cast 操作,可以强制改变变量的数据类型,比如把32位整数强行改变为16位整数。[图片上传中...(bg2022060114.jpg-7a8cc9-1701243293826-0)]

bg2022060114.jpg

上图中,32位整数0x00000001更改为16位整数0x0001,大端序是截去前面两个字节,这时指向这个地址的指针必须向后移动两个字节。
小端序就没有这个问题,截去的是后面两个字节,第一位的地址是不变的,所以指针不需要移动。

网络字节序和主机字节序

网络字节序:TCP/IP各层协议将字节序定义为Big Endian,即大端模式,TCP/IP协议中使用的字节序是大端序,方便不同主机字节序的设备进行网络传输数据。
主机字节序:整数在内存中存储的顺序,目前以Little Endian,即小端模式,比较普遍(不同的CPU有不同的字节序)。iOS、macOS都是小端序。

注意:不少文章说macOS是大端序,是错误的(参考:将 macOS App 移植到 Apple 芯片 - Apple Developer )。

image 3.jpg

总结:
如果需要逐位运算,或者需要到从个位数开始运算,都是小端序占优势。反之,如果运算只涉及到高位,或者数据的可读性比较重要,则是大端序占优势。
一些硬件厂商的坚持,因此在多字节存储顺序上始终没有一个统一的标准。

如何判断大小端?

通过读取低位地址
#include <stdio.h>

int main() {
    __uint16_t val = 0x1234;

    char a = ((char *) &val)[0]; // 低位地址
    char b = ((char *) &val)[1]; // 高位地址

    printf("a = %x\n", a);
    printf("b = %x\n", b);

    if (a == 0x34) {
        printf("小端模式\n");
    } else {
        printf("大端模式\n");
    }

    return 0;
}
利用联合体

联合体是一种特殊的数据结构,联合体中的成员变量共用同一段内存。
我们定义一个 test 联合体,设置两个成员变量 a 和 b。

#include <stdio.h>

int main()
{
    union test {
        __uint32_t a;
        char b;
    };

    union test val;

    val.a = 0x12345678;

    printf("%x\n", val.b);

    if (val.b == 0x78) {
        printf("小端模式\n");
    } else {
        printf("大端模式\n");
    }

    return 0;
}
C 语言内置宏
// 小端模式
# define LITTLE_ENDIAN  __LITTLE_ENDIAN
// 大端模式
# define BIG_ENDIAN __BIG_ENDIAN
// 当前主机的字节序
# define BYTE_ORDER __BYTE_ORDER
#include <endian.h>

int main()
{
    if (BYTE_ORDER == LITTLE_ENDIAN) {
        printf("小端模式\n");
    } else {
        printf("大端模式\n");
    }

    return 0;
}

大小端转换

手动实现转换逻辑
只需要将高位字节与低位字节进行交换,就可以实现大小端的转换。

int main()
{
    __uint32_t val = 0x12345678;

    unsigned char *x = (unsigned char *) &val, tmp;

    // 0x78 与 0x12 进行交换
    tmp = x[0];
    x[0] = x[3];
    x[3] = tmp;

    // 0x56 与 0x34 交换
    tmp = x[1];
    x[1] = x[2];
    x[2] = tmp;

    // 输出:0x78563412
    printf("0x%x\n", val);

    return 0;
}
C 语言内置宏
// 转换 16 位整数
htobe16(x)
be16toh(x)

// 转换 32 位整数
htobe32(x)
be32toh(x)

// 转换 64 位整数
htobe64(x)
be64toh(x)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 大小端定义 大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的...
    conowen阅读 6,006评论 2 0
  • 内存逻辑上是一个大的字节数组,当存储大于一个字节的数据时就有字节序的问题。 大小端经常弄混,这里有种简单的记忆方法...
    lesliefang阅读 538评论 0 1
  • 0. 一句话总结 从左向右读(从低地址到高地址),先遇到权重“大”的就是“大”端字节序,先遇到权重“小”的就是“小...
    程序员不太冷阅读 705评论 0 0
  • 字节序,或字节顺序("Endian"、"endianness" 或 "byte-order"),描述了计算机如何组...
    _瑾_阅读 150评论 0 0
  • 大端和小端 big endian(大端法)是指低地址存放最高有效字节(MSB),而little endian(小端...
    右手甜蜜阅读 2,806评论 0 0