什么是字节序?
字节序就是数据存放的顺序。当数据仅有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