前几个月面试某公司的 C++ 开发岗位时,面试官问了我主机字节序和网络字节序的问题。今天突然想起这个问题,故写此博文来学习和记录这个问题。
问题
面试官问我问题如下:
- 两种字节序的区别,即大端和小端的区别;
- 主机字节序和网络字节序如何转换;
- 如何判断一台机器的字节序?
我的回答为:
-
大端字节序将高位字节存储在内存低地址处;小端字节序则将低位字节存储在内存高地址处
-
现代大部分 CPU 采用的是小端字节序,网络字节序默认采用大端字节序,因此在编程的时候很有可能需要进行字节序之间的转换。Linux 下的 socket API 提供了四个函数用于网络字节序和主机字节序之间的转换。
-
我的回答为位运算,对于某个数字,例如数字
0x0102
,依次取每个字节和0b1111111
做按位与得到的结果,看结果满足哪种字节序。
面试结束后,面试官说我的第三题说的方法是错误的。现在自己试着写了一边代码验证,其实这个做法是正确的。代码如下:
#include <stdio.h>
#include <netinet/in.h>
void byteorder()
{
unsigned short value = 0x0102;
// value = htons(value); // 转换为大端序(网络字节序)
unsigned short bitmask = (1 << 9) - 1;
if ( ((char)(value & bitmask)) == 1 && ((char)((value >> 8) & bitmask)) == 2 )
printf("big endian\n");
else if ( ((char)(value & bitmask)) == 2 && ((char)((value >> 8) & bitmask)) == 1 )
printf("little endian\n");
else
printf("unkown...\n");
}
网络上发现了一个更好的做法,使用了共用体 union
来避免位运算,巧用共用体来改变字节的解释方式, 这个方法真是妙:
#include <stdio.h>
void byteorder()
{
union {
short value;
char union_bytes[ sizeof(short) ];
} test;
test.value = 0x0102;
if ( (test.union_bytes[0] == 1) && (test.union_bytes[1] == 2) )
printf("big endian\n");
else if ( (test.union_bytes[0]) == 2 && (test.union_bytes[1] == 1) )
printf("little endian\n");
else
printf("unkown...\n");
}
总结
以 load 一个四字节整数为例,这四个字节在内存中排列的顺序影响它被累加器装载成整数的值,这就产生了字节序的问题。字节序分为大端字节序和小端字节序。大端字节序指的是将整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。
为什么小端字节序更受欢迎呢?因为计算机底层逻辑电路先处理低位字节,这一细节更加符合小端字节序。