字节序-大小端

“Computers are useless.They can only give you answers.” —— Pablo Picasso

字节序

endian 起源

摘自维基百科

“endian”一词来源于十八世纪爱尔兰作家乔纳森·斯威夫特(Jonathan Swift)的小说《格列佛游记》(Gulliver’s Travels)。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派”和“小端派”。以下是1726年关于大小端之争历史的描述:

“我下面要告诉你的是,Lilliput 和 Blefuscu 这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由 Blefuscu 的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。”
                         ——《格列夫游记》 第一卷第4章 蒋剑锋(译)

1980年,丹尼·科恩(Danny Cohen),一位网络协议的早期开发者,在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而第一次引用了该词。


字节序概念

字节序(Endianness)是一个术语,它描述了字节序列在计算机内存中的存储顺序。字节的排列方式有两个通用的规则。例如,一个多字节数据的低位存储在低存储地址处,高位放在高存储地址处,则被称为小端(little-endian),相反,将高位存储在低存储地址处,低位放在高存储地址处,则称为大端(big-endian)。

在应用中,字节序是一个必须被考虑的因素,因为不同的机器类型可能采用不同标准的字节序,所以在实际应用中,必须知道要采用哪种字节排列方式。


大端与小端

概念

大端,是指在多字节数据类型中,将数据的高字节保存在内存的低地址中,而数据的低字节保存在高地址中。
小端,是指在多字节数据类型中,将数据的高字节保存在内存的高地址中,而数据的低字节保存在低地址中。

假设一个整数被存储为 4 个字节(32 位),那么一个值为 0x01234567 的变量 y 存储时被分为四个字节 0x01、0x23、0x45、0x67,在不同字节序大端或小端规则中,它将以相反的顺序存储:

如上图,地址从低地址 0x100 往 高地址 0x103 递增,大端规则中,多字节数据的高位放在了低地址中,而小端规则中,则顺序相反,将多字节数据的高位放在了高地址中。


哪个更好

  • 小端
    • 更易于实现加、减、乘这类常用运算
    • 在特定场合能减少对数据的读取次数
    • 对于低位数据的处理更友好,不重要的字节保留在远处,新的字节则添加到更高地址。
  • 大端
    • 对从左到右阅读更为友好,以正向的方式存储,对网络通信数据包的处理更友好(网络通信的TCP/IP协议中字节序就是按照大端规定的)
    • 更易于实现对同等长度变量做比较运算和除法运算
    • 对符号位的判断更便捷(由于高位字节在前,可以通过查看偏移量零处的字节来查看数字是正数还是负数,不必知道数字有多长,也不必跳过任何字节来查找包含符号信息的字节)

小端和大端哪个更好是无法达成一致的,就像《格列佛游记》中鸡蛋应该在哪一端被打开一样,没有技术理由来规定我们只使用一种字节的排序约定,只要选择并始终如一地遵守其中一种约定,选择就是任意的。


如何知道当前属于大端还是小端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void show_mem_rep(char *start, int n)
{
int i;
for (i = 0; i < n; i++)
printf(" %.2x", start[i]);
printf("\n");
}

int main()
{
int i = 0x01234567;
show_mem_rep((char *)&i, sizeof(i));
return 0;
}

上述程序在小端机器上运行时,输出“67 45 23 01”,而在大端机器上运行时,输出“01 23 45 67”。


大小端如何相互转化

1
2
3
4
//16bit
#define Convert16(A) ((((uint16)(A) & 0xff00) >> 8) | (((uint16)(A) & 0x00ff) << 8))
//32bit
#define Convert32(A) ((((uint32)(A) & 0xff000000)>>24) | (((uint32)(A) & 0x00ff0000)>>8) | (((uint32)(A) & 0x0000ff00)<<8) | (((uint32)(A) & 0x000000ff)<<24))