前几个月面试某公司的 C++ 开发岗位时,面试官问了我主机字节序网络字节序的问题。今天突然想起这个问题,故写此博文来学习和记录这个问题。

问题

面试官问我问题如下:

  1. 两种字节序的区别,即大端和小端的区别;
  2. 主机字节序和网络字节序如何转换;
  3. 如何判断一台机器的字节序?

我的回答为:

  1. 大端字节序将高位字节存储在内存低地址处;小端字节序则将低位字节存储在内存高地址处

  2. 现代大部分 CPU 采用的是小端字节序,网络字节序默认采用大端字节序,因此在编程的时候很有可能需要进行字节序之间的转换。Linux 下的 socket API 提供了四个函数用于网络字节序和主机字节序之间的转换。

  3. 我的回答为位运算,对于某个数字,例如数字 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 一个四字节整数为例,这四个字节在内存中排列的顺序影响它被累加器装载成整数的值,这就产生了字节序的问题。字节序分为大端字节序和小端字节序。大端字节序指的是将整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。

为什么小端字节序更受欢迎呢?因为计算机底层逻辑电路先处理低位字节,这一细节更加符合小端字节序。