我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
在网络编程的时侯,因为端口号要进行字节序列的转换,涉及endian:
Big endian: 当存储一个超过一个字节的数据类型时,将其中的高位放在低地址的地方,低位放在高地址的地方。
Little endian: 于Big endian恰恰相反。
File: include/linux/ip.h
82 struct iphdr { 83 #if defined(__LITTLE_ENDIAN_BITFIELD) 84 __u8 ihl:4, 85 version:4; 86 #elif defined (__BIG_ENDIAN_BITFIELD) 87 __u8 version:4, 88 ihl:4; 89 #else 90 #error "Please fix <asm/byteorder.h>" 91 #endif 92 __u8 tos; 93 __be16 tot_len; 94 __be16 id; 95 __be16 frag_off; 96 __u8 ttl; 97 __u8 protocol; 98 __u16 check; 99 __be32 saddr; 100 __be32 daddr; 101 /*The options start here. */ 102 }; |
当看到83-91行的代码的时侯,难道在小于自己单位的bit之中也存在endian的问题?
当需要用多个字节来描述一个数据类型,或者是一个数据结构中包含多个字段时,各个字段和存储区块中的地址的映射关系如下:
- big endian: 存储的区块按照地址从低到高的顺序以字节为单位从左到右排列,划分字段的时侯,从左到右依次划分。
- little endian: 存储的区块按照地址从高到低的顺序以字节为单位从左到右排列,划分的时侯,从右到左依次划分。
现在简单的给大家举几个简单的例子:
u_int16_t x = 0x1; u_int8_t xx[2]; memcpy(xx, x); |
在上面的代码中,最终xx[0]和xx[1]都是什么呢?
答案是不固定的,如果是在big endian的计算机上:
应用第一条规则,从xx中从取出两个字节x[0], x[1]。我们知道x[1]的地址肯定比x[0]高,所以排列如下:
x[0] x[1] --------- 15 ... 0 --------- 00 01 |
很明显x[0] == 0, x[1] == 1.
如果是little endian的计算机:
应用第二条规则:
x[1] x[0] --------- 15 ... 0 --------- 00 01 |
结果为x[0] == 1, x[1] == 0.
仔细看看结论是不是和在文章开始的判别方法给出的结论是相同的?所以在一定程度上,我们可以认为前一个结论是后一个结论的推论。
让我们看个更加复杂一点的例子:
union { u_int16_t num; struct { u_int8_t a4 : 4; u_int8_t b8; u_int8_t c4 : 4; } b; } a; a.b.a4 = 0x01; a.b.b8 = 0x0203; a.b.c4 = 0x04; a.num = ? |
这个是不是复杂了很多,不要说实际中这样的应用不多,看看Linux内核中数据结构你就会明白,类似结构还是蛮多的。
当然又是和endian有关的问题,分类分析了:
big endian:
--------------------- | 7-4|3-0 | 7-4|3-0 | --------------------- | a[0] | a[1] | --------------------- | a4 | b8 | c4 | --------------------- | 01 | 02 | 03 | 04 | --------------------- | 0x01020304 | --------------------- |
所以,a.num = 0x01020304
little endian:
--------------------- | 7-4|3-0 | 7-4|3-0 | --------------------- | a[1] | a[0] | --------------------- | c4 | b8 | a4 | --------------------- | 04 | 02 | 03 | 01 | --------------------- | 0x04020301 | --------------------- |
所以,a.num = 0x04020301