概览
在计算机系统中,常以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器)。另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
我们常用的X86结构、ARM、DSP都是小端模式,而嵌入式系统开发中的KELI C51则为大端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
大小端含义
所谓的大端模式(Big Endian,简称BE),是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放。
所谓的小端模式(Little Endian,简称LE),是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
补充说明,PDP模式(PDP endian)是一种极为少见的一种模式,比如对于4字节,高位字节存储顺序分别第3、第4、第1、第2。
举例说明
假设存储地址是从0x4000开始,那么16bit宽的数0x1234分别在大小端模式下存储如下所示:
内存地址 | 0x4000 | 0x4001 |
---|---|---|
小端模式 | 0x34 | 0x12 |
大端模式 | 0x12 | 0x34 |
在GLIb中,LE、BE、PDP的定义分别如下:
#define G_LITTLE_ENDIAN 1234
#define G_BIG_ENDIAN 4321
#define G_PDP_ENDIAN 3412
大小端存储模式引发的问题
对于大多数程序员而言,机器的字节存储顺序是完全不可见的,无论哪一种存储模式的处理器编译出的程序都会得到相同的结果。即对于同一段源代码,单独在小端机器上编译运行,其结果与单独在大端机器上编译运行的结果一样,尽管同一个数据在大小端格式下的内存表示有区别,但在应用程序员和用户眼里,参与算术逻辑运算、写入读出的数据却是无差别的。不过,有些情况下,字节顺序会成为问题。
- UNIX问题———程序可移植性问题。
- 阅读、解释、共享二进制数据的问题。对同一段可执行程序进行反汇编,使用反汇编器阅读机器级二进制代码时,在不同存储模式的处理器上会看到不同的结果。
- 网络数据传输的问题。通过网络传送二进制数据时,可能会产生高低字节翻转现象。
Glib大小端相关宏
本地与网络间转换
g_htonl与g_htons分别用于将32位、16位的本地整数转为网络字节顺序。g_ntohl、g_ntohs反过来。
g_htonl(val)
g_htons(val)
g_ntohl(val)
g_ntohs(val)
其中,h表示本地host, n表示网络network,s表示short。
各种类型整数的本地模式与大小端间的转换
Glib提供了丰富的宏用于整数在各种大小端间的转换,具体包括INT、UINT、LONG、ULONG、SIZE、INT16、UINT16、INT32、UINT32、INT64、UINT64。
提供的FROM表示从某种模式转为本地模式,TO表示从本地模式转为某种模式,具体由后面的BE或LE来确定。其中,BE表示Big-Endian大端模式,LE表示Little-Endian小端模式。
GINT_FROM_BE()、GINT_FROM_LE()、GINT_TO_BE()、GINT_TO_LE()
GUINT_FROM_BE()、GUINT_FROM_LE()、GUINT_TO_BE()、GUINT_TO_LE()
GLONG_FROM_BE()、GLONG_FROM_LE()、GLONG_TO_BE()、GLONG_TO_LE()
GULONG_FROM_BE()、GULONG_FROM_LE()、GULONG_TO_BE()、GULONG_TO_LE()
GSIZE_FROM_BE()、GSIZE_FROM_LE()、GSIZE_TO_BE()、GSIZE_TO_LE()
GSSIZE_FROM_BE()、GSSIZE_FROM_LE()、GSSIZE_TO_BE()、GSSIZE_TO_LE()
GINT16_FROM_BE()、GINT16_FROM_LE()、GINT16_TO_BE()、GINT16_TO_LE()、
GUINT16_FROM_BE()、GUINT16_FROM_LE()、GUINT16_TO_BE()、GUINT16_TO_LE()
GINT32_FROM_BE()、GINT32_FROM_LE()、GINT32_TO_BE()、GINT32_TO_LE()
GUINT32_FROM_BE()、GUINT32_FROM_LE()、GUINT32_TO_BE()、GUINT32_TO_LE()
GINT64_FROM_BE()、GINT64_FROM_LE()、GINT64_TO_BE()、GINT64_TO_LE()
GUINT64_FROM_BE()、GUINT64_FROM_LE()、GUINT64_TO_BE()、GUINT64_TO_LE()
补充说明
为了方便BE、LE、PDP之间的相互转换,又提供的又下述几种宏,用于相互转换。比如GUINT16_SWAP_BE_PDP(val),如果val是BE类型,则结果为PDP类型;如果val是PDP类型,则结果为BE类型。
GUINT16_SWAP_BE_PDP()、GUINT16_SWAP_LE_BE()、GUINT16_SWAP_LE_PDP()
GUINT32_SWAP_BE_PDP()、GUINT32_SWAP_LE_BE()、GUINT32_SWAP_LE_PDP()
GUINT64_SWAP_LE_BE()
上述中,swap表示交换的意思。
补充资料
巧用联合体对大小端判断
#include <glib.h>
union Endian{
int a;
char b;
} endian;
int main(int argc, char const *argv[]) {
endian.a = 1;
if (endian.b == 1) {
g_print("little endian\n");
} else {
g_print("big endian\n");
}
}
// 输出
little endian
由于共用体中的所有元素都存放在一块内存空间中,上述联合体Endian中,字符b的起始地址是从int型的起始地址对齐的。所以对一个int型的数据进行写1,如果是大端的话,char型的b就会为1,否则b就为0。
小结
总而言之,采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。