什么是字节序?

字节序就是数据存放的顺序。当数据仅有1字节时,计算机无需考虑字节存放顺序;但当数据大于1字节时,就必须考虑如何存放了(先放高字节还是低字节),如十六进制数0x12345678,按人类阅读习惯,左起为高字节,右起为低字节:

|高字节--------------->低字节|
|------|------|------|------|
| 0x12 | 0x34 | 0x56 | 0x78 |
|------|------|------|------|

0x12为高字节,0x78为低字节。

内存地址连续排列,十六进制数0x12345678占用4字节,在不同体系架构下,数据存放方式略有差异,分为大端字节序小端字节序

大端序

符合人的阅读习惯,低字节存放在高地址高字节存放在低地址,即内存排布如下:

|高字节--------------->低字节|
|------|------|------|------|
| 0x12 | 0x34 | 0x56 | 0x78 |
|------|------|------|------|
|低地址--------------->高地址|

小端序

低字节存放在低地址高字节存放在高地址,即内存排布如下:

|低字节--------------->高字节|
|------|------|------|------|
| 0x78 | 0x56 | 0x34 | 0x12 |
|------|------|------|------|
|低地址--------------->高地址|

什么时候需要关注字节序?

普遍场景是网络编程有数据交互时,网络字节序统一为大端序,而大多x86机器为小端序,此时就需要字节序转换(只在必要时转换,如socket端口号无需所有数据都转一遍),如果程序只是在机器本地运行则一般不考虑。

运行时判断机器字节序

以下分别通过指针和共用体判断字节序

#include <stdio.h>
/** \brief 通过指针操作判断机器字节序是否为小端序
 *
 * \return 小端序返回1,大端序返回0
 *
 */
int is_little_endian(void)
{
    int data= 1;//用1可移植性更好,int为4字节时等价于0x00000001

    //printf("data 0x%08x in memory\n", data);
    //printf("first byte data is : 0x%08x\n", *(char *)&data);

    /**< 低字节放在低地址,低地址即data变量首地址,即小端序 */
    if(1 == *(char *)&data)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/** \brief 通过共用体判断机器字节序是否为小端序
 *
 * \return 小端序返回1,大端序返回0
 *
 */
int is_little_endian_2()
{
    union endian
    {
        int i;
        char ch;
    };

    union endian test;
    test.i=1;//int为4字节时等价于0x00000001

    //printf("data 0x%08x in memory\n", test.i);
    //printf("first byte data is : 0x%08x\n", test.ch);

    if(1 == test.ch)
    {
        return 1;
    }
    else
    {
        return 0;
    }

}

自定义字节序转换方法

原理就是根据数据的位数进行移位操作,示例:32位整数的转换

/** \brief 32位整数大小端转换,仅在小端时转换,使用了前面定义的is_little_endian()函数
 */
#define L2B_32(data) (1 == is_little_endian() ? (( (data & 0xff000000) >>24) |   \
                                                  ((data & 0x00ff0000) >>8)  |   \
                                                  ((data & 0x0000ff00) <<8)  |   \
                                                  ((data & 0x000000ff) <<24) )   \
                                                : data )

对比两种字节序可以发现无非就是:高字节移动到低字节,低字节移动到高字节

假设小端字节序保存的数据为0x12345678,即| 0x12 | 0x34 | 0x56 | 0x78 |

转换为大端的结果为| 0x78 | 0x56 | 0x34 | 0x12 |

0x78向左移动了3个字节即24位

0x56向左移动了1个字节即8位

同理:

0x34向右移动了1个字节即8位

0x12向左移动了3个字节即24位

L2B_32(data)宏定义用了三木运算符? :,实现当判断机器为小端字节序时自动做转换,判断机器为大端时不做转换。

以上示例实现了int的字节序转换,short、long long等类型操作类似,不再赘述。

验证

以下测试程序使用x86机器(小端)运行

int main() {

    printf("%#x\n", L2B_32(0x12345678));

	return 0;
}

运行结果

0x78563412