鸡蛋的两种吃法 : 字节顺序
目录
1 字节顺序
鸡蛋有几种吃法?也许你从未注意。Jonathan Swift 的小说 Gulliver's Travels 描写了这么一个故事: Lilliput 国的皇帝因按古法打鸡蛋时弄破的手指,于是下令全体臣民吃鸡蛋时必须先打破鸡蛋较小的一端(little-endian)。但百姓这对项命令本极度反感,并为此发动叛乱。
在计算机时代,计算机网络的开创者之一, Danny Cohen 开始使用 Swift 的小说中的词语 大端、小端 来描述字节的顺序,大小端这一术语开始被人们所接纳。 在计算机时代的远古时期,计算机刚被发明的时候,由于不能统一规则,对多字节对象在存储器上的存储方式有两种不同的方式。但它们有一个共识,即,多字节的对象都被存储为连续的字节序列,而对象的地址则这段连续序列中的最小的字节的地址。
例如,一个 int 类型的变量 n 的地址为 0x100,即 &n == 0x100,那么 n 将被存储在 0x100,0x101,0x102,0x103 这段位置。 假定有一个 w 位的整数,其 位 表示为 [bw-1, bw-2, … ,b1, b0] , 其中 bw-1 为最高位,b0 为最低位。这些位按每 8 位,即一个字节分组, 表示为 [Bx-1, Bx-2, … , B1, B0] ,其中 Bn = [bn*8-1, bn*8-2, … , bn*8-8] 。 有的机器选择选择在存储器按照从低字节到高字节的顺序来存储,这种方式称为 小端法(little-endian)
,基本所有的 Intel 机器都采用这种方式。而别一部分机器则选择按照高字节到低字节的方式来存储,称为 大端法(big-endian)
,大部分 IBM 和 Sun 的机器都采用这种规则。
所以,当一段数据从使用小端规则的机器传送到使用大端规则的机器上时,数据是无法正确解析的。除非明确的告诉计算机,这段数据需要使用哪种规则来解析。这计算机网络发明以后,这种不兼容的带来的负面作用显得尤为突出。于是一个规则产生了,在网络传输过程中,数据的发送方必须将多字节数据转换成大端法表示后再发送。而数据的接收方则需要按照大端法来解析多字节数据,再转换为它的内部表示。这样,在网络中,终端只关心本机的字节序与网络字节序,而不用关心网络另一端的机器使用什么样的字节充。所以又将大端法称为 网络字节序
,以区分 本机字节序
。
以下 c 代码在 Windows 上运行:
1 2 3 4 5 6 7 |
unsigned short num = 3; num = (num << 8) + 1; // 00000011 00000001 unsigned char* c = reinterpret_cast<unsigned char*>(&num); printf("%d , %d ", c[0], c[1]); // Output 1 , 3 |
可以看到,对于占两个字节的 unsigned short 类型数据,Windows 将高位字节 [ 00000011 ] 存储在后面,而低位字节 [ 00000001 ] 存储在前面, num = 769 这个数据在存储器中的布局为 [ 0000001 00000011 ].
2 字节序转换
Linux 和 Windows 提供字节序转换的 API:
ntohs
: net to host short int , 16 位htons
: host to net short int , 16 位ntohl
: net to host long int , 32 位htonl
: host to net long int , 32 位
boost 中相应的 API:
u_long_type network_to_host_long(u_long_type value)
long_type host_to_network_long(u_long_type value)
u_short_type network_to_host_short(u_short_type value)
u_short_type host_to_network_short(u_short_type value)
在 python 中则可以使用 struct:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import os import struct num = 3 num = (num << 8) + 1 def data_to_memlist(fmt, data): return map(lambda x: ord(x), struct.pack(fmt, data)) print data_to_memlist('@H',num) #local print data_to_memlist('<H',num) #little-endian print data_to_memlist('>H',num) #big-endian |
pack/unpack 可以将数据按 fmt 指定的格式进行序列化. fmt 由两个字符组成,第一个字段表示字节序,第二个字段表示数据类型。在官方文档里有详细的说明。
第一个字符的意义如下:
Character | Byte order | Size | Alignment |
---|---|---|---|
@ |
native | native | native |
= |
native | standard | none |
< |
little-endian | standard | none |
> |
big-endian | standard | none |
! |
network (= big-endian) | standard | none |
第二个字符的意义如下:
Format | C Type | Python type | Standard size |
---|---|---|---|
x |
pad byte | no value | |
c |
char |
string of length 1 | 1 |
b |
signed char |
integer | 1 |
B |
unsigned char |
integer | 1 |
? |
_Bool |
bool | 1 |
h |
short |
integer | 2 |
H |
unsigned short |
integer | 2 |
i |
int |
integer | 4 |
I |
unsigned int |
integer | 4 |
l |
long |
integer | 4 |
L |
unsigned long |
integer | 4 |
q |
long long |
integer | 8 |
Q |
unsigned long long |
integer | 8 |
f |
float |
float | 4 |
d |
double |
float | 8 |
s |
char[] |
string | |
p |
char[] |
string | |
P |
void * |
integer |