« | September 2025 | » | 日 | 一 | 二 | 三 | 四 | 五 | 六 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | | | | | |
| 公告 |
|
Blog信息 |
blog名称:FoxWolf 日志总数:127 评论数量:246 留言数量:0 访问次数:854952 建立时间:2006年5月31日 |

| |
[嵌入式学习]big-endian and little-endian 文章收藏, 软件技术, 电脑与网络
FoxWolf 发表于 2007/6/23 14:52:36 |
1.故事的起源
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
2.什么是Big Endian和Little Endian?
在设计计算机系统的时候,有两种处理内存中数据的方法。一种叫为little-endian,存放在内存中最低位的数值是来自数据的最右边部分(也就是数据的最低位部分)。比如一个16进制数字0x12345678, 在内存存放的方式如下:
值
0111,1000
0101,0110
0011,0100
0001,0010
地址
100
101
102
103
另一种称为big-endian,正好相反,存放在内存中最低位的数值是来自数据的最左边边部分(也就是数据的最高为部分)。比如一个16进制数字0x12345678, 在内存存放的方式如下:
值
0001,0010
0011,0100
0101,0110
0111,1000
地址
100
101
102
103
比如某些文件需要在不同平台处理,或者通过Socket通信。这方面我们可以借助ntohl(), ntohs(), htonl(), and htons()函数进行格式转换。
3.如何判断系统是Big Endian还是Little Endian?
在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),确定其值。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。一般来说,Little Endian系统BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big Endian系统为4321。大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本质上说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。
============================================================================================
big-endian and little-endian
谈到字节序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,什么又是 little endian呢?
其实big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。
用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
低地址 高地址 -----------------------------------------> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 12 | 34 | 56 | 78 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
低地址 高地址 -----------------------------------------> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 78 | 56 | 34 | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的。而little endian,!@#$%^&*,见鬼去吧 -_-|||
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而 JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA 采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI C中提供了下面四个转换字节序的宏。 ·BE和LE一文的补完
我在8月9号的《Big Endian和Little Endian》一文中谈了字节序的问题,原文见上面的超级链接。可是有朋友仍然会问,CPU存储一个字节的数据时其字节内的8个比特之间的顺序是否也有big endian和little endian之分?或者说是否有比特序的不同?
实际上,这个比特序是同样存在的。下面以数字0xB4(10110100)用图加以说明。
Big Endian
msb lsb ----------------------------------------------> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
lsb msb ----------------------------------------------> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
实际上,由于CPU存储数据操作的最小单位是一个字节,其内部的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给我一个指向0xB4这个数的指针,对于big endian方式的CPU来说,它是从左往右依次读取这个数的8个比特;而对于little endian方式的CPU来说,则正好相反,是从右往左依次读取这个数的8个比特。而我们的程序通过这个指针访问后得到的数就是0xB4,字节内部的比特序对于程序来说是不可见的,其实这点对于单机上的字节序来说也是一样的。
那可能有人又会问,如果是网络传输呢?会不会出问题?是不是也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设little endian方式的CPU要传给big endian方式CPU一个字节的话,其本身在传输之前会在本地就读出这个8比特的数,然后再按照网络字节序的顺序来传输这8个比特,这样的话到了接收端不会出现任何问题。而假如要传输一个32比特的数的话,由于这个数在littel endian方存储时占了4个字节,而网络传输是以字节为单位进行的,little endian方的CPU读出第一个字节后发送,实际上这个字节是原数的LSB,到了接收方反倒成了MSB从而发生混乱。
============================================================================================
一个16位整数,由两个字节组成。内存中存储这两个字节有两种方法:一种是将低序字节存储在起始地址,称为little-endian字节序,另一种方法是将高序字节存储在起始地址,称为big-endian字节序。术语little-endian和big-endian表示多字节的哪一端存储在该值的起始地址。
例如:0x0102
little-endian: A 0x02
A+1 0x01
big-endian: A 0x01
A+1 0x02
UNP上面有测试字节排序的函数。考虑到网络字节序为big-endian,修改程序来比较一下Host Byte Order和Internet Byte Order的区别。
/* * filename: byteorder.c */#include <stdio.h>#include <netinet/in.h>union { short s; char c[sizeof(short)];}un;short test = 0x0203;void byteorder(char *msg){ printf("%s", msg); if (sizeof(short) == 2) { if (un.c[1] == (test & 0x00ff)) { printf("big-endian\n"); } else if (un.c[0] == (test & 0x00ff)) { printf("small-endian\n"); } else { printf("unkowned\n"); } } else { printf("sizeof(short) = %d\n", sizeof(short)); }}int main(void){ char *msg1 = "Host Byte Order: "; char *msg2 = "Network Byte Order: "; un.s = test; byteorder(msg1); un.s = htons(test); byteorder(msg2); return 0;}
测试结果如下:
[armlinux@lqm byteorder]$ ./byteorder Host Byte Order: small-endianNetwork Byte Order: big-endian
在进行嵌入式移植的时候,要考虑到字节序的问题。网络编程更是要考虑字节序的转换,一般使用四个函数htons、htonl、ntohs、ntohl。其中,h代表host,n代表network,s代表short,l代表long。在这里,把s看成一个16位的值(如TCP或UDP的端口号),把l看作32位的值(如IPv4)。要进行IPv6的编程的话,需要进行相应的改变了。
===============================================================================================
little endian和big endian是表示计算机字节顺序的两种格式,所谓的字节顺序指的是长度跨越多个字节的数据的存放形式. 假设从地址0x00000000开始的一个字中保存有数据0x1234abcd,那么在两种不同的内存顺序的机器上从字节的角度去看的话分别表示为: 1)little endian:在内存中的存放顺序是0x00000000-0xcd,0x00000001-0xab,0x00000002-0x34,0x00000003-0x12 2)big endian:在内存中的存放顺序是0x00000000-0x12,0x00000001-0x34,0x00000002-0xab,0x00000003-0xcd 需要特别说明的是,以上假设机器是每个内存单元以8位即一个字节为单位的. 简单的说,ittle endian把低字节存放在内存的低位;而big endian将低字节存放在内存的高位. 现在主流的CPU,intel系列的是采用的little endian的格式存放数据,而motorola系列的CPU采用的是big endian. 以下是判断字节存储顺序的可移植的C语言代码:
/******************************************************************** created: 2006-9-5 filename: test.cpp author: 李创 purpose: 可移植的用于判断存储格式是 little endian还是big ednian的C代码 取自<<C: A Reference Manual>>*********************************************************************/
#include <stdio.h>
union{ long Long; char Char[sizeof(long)];}u;
int main(){ u.Long = 1; if (u.Char[0] == 1) { printf("Little Endian!\n"); } else if (u.Char[sizeof(long) - 1] == 1) { printf("Big Endian!\n"); } else { printf("Unknown Addressing!\n"); }
printf("Now, Let's look at every byte in the memory!\n"); for (int i = 0; i < sizeof(long); ++i) { printf("[%x] = %x\n", &u.Char, u.Char); }
return 0;}
很多人认为掌握这个知识是不必要,其实不然.在网络编程中,TCP/IP统一采用big endian方式传送数据,也就是说,假设现在是在一个字节顺序是little endian的机器上传送数据,要求传送的数据是0XCEFABOBO,那么你就要以0XBOBOFACE的顺序在unsigned int中存放这个数据,只有这样才能保证存放的顺序满足TCP/IP的字节顺序要求.很多时候,需要自己编写应用层的协议,字节顺序的概念在这个时候就显得及其的重要了. 下面给出的是在big endian和little endian中相互转换的代码,C语言强大的位操作的能力在这里显示了出来:
/******************************************************************** created: 2006-9-5 filename: get32put32.cpp author: 李创 purpose: 在little endian和big ednian之间相互转化数据的演示代码
*********************************************************************/
#include <stdio.h>
const unsigned char SIZE_OF_UNSIGNEDINT = sizeof(unsigned int);const unsigned char SIZE_OF_UNSIGNEDCHAR = sizeof(unsigned char);
void put_32(unsigned char *cmd, unsigned int data){ int i; for (i = SIZE_OF_UNSIGNEDINT - 1; i >= 0; --i) { cmd = data % 256; // 或者可以: //cmd = data & 0xFF; data = data >> 8; }}
unsigned int get_32(unsigned char *cmd){ unsigned int ret; int i;
for (ret = 0, i = SIZE_OF_UNSIGNEDINT - 1; i >= 0; --i) { ret = ret << 8; ret |= cmd; } return ret;}
int main(void){ unsigned char cmd[SIZE_OF_UNSIGNEDINT]; unsigned int data, ret; unsigned char *p; int i;
data = 0x12345678; printf("data = %x\n", data); // 以字节为单位打印出数据 p = (unsigned char*)(&data); for (i = 0; i < SIZE_OF_UNSIGNEDINT; ++i) { printf("%x", *p++); } printf("\n");
// 以相反的顺序存放到cmd之中 put_32(cmd, data); for (i = 0; i < SIZE_OF_UNSIGNEDINT; ++i) { printf("cmd[%d] = %x\n", i, cmd); }
// 再以相反的顺序保存数据到ret中 // 保存之后的ret数值应该与data相同 ret = get_32(cmd); printf("ret = %x\n", ret); p = (unsigned char*)(&ret); for (i = 0; i < SIZE_OF_UNSIGNEDINT; ++i) { printf("%x", *p++); } printf("\n");
return 0;}
参考资料:<<C: A Reference Manual>>
低字节和高字节的说法是不严谨的。
0x11223344 对于 little-endian 来说,0x44是低字节,对 big-endian 来说,0x11是低字节
严谨的用词是: MSB 与 LSB, 也就是说: 对于 little-endian 来说 MSB 在高地址,对 big-endian 来说 MSB 在低地址。 |
|
|