文先生的博客 求职,坐标深圳。(wenfh2020@126.com)

字节序转换关系

2021-07-11

梳理一下字节序的转换关系:大端字节序(big endian),小端字节序(little endian)和网络字节序。


1. 概述

  • 大小端。

    计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。

    考虑一个 16 位整数,它由 2 个字节组成。内存中存储这两个字节有两种方法:

    1. 小端:将低序字节存储在起始地址;
    2. 大端:将高序字节存储在起始地址。

    上述文字主要来源于:《UNIX 网络编程》- 3.4 字节排序函数。

  • 网络字节序。

    网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用大端(big-endian)排序方式。

    上述文字主要来源于:网络字节序


2. 字节序关系

  • 字节序转换函数。
1
2
3
4
5
6
7
8
9
#include <netinet/in.h>

/* 返回网络字节序的值。 */
uint16_t htons(uint16_t host16bitvalude);
uint32_t htonl(uint32_t host32bitvalude);

/* 返回主机字节序的值。 */
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

  • 转换流程。

    举个 🌰:client 与 server 通信,16 位整数字节序转换流程:htons –> 网络字节序(大端) –> ntohs。

    其实这里面有两个环节:

    1. (htons)客户端主机字节序转网络字节序。
    2. (ntohs)网络字节序转服务器主机字节序。

我们再看看 glibc 字节序转换的源码实现,htons 和 ntohs 居然指向同一个函数。

小结:网络字节序默认是大端的,只要 主机字节序是小端的 ,在传输过程中都要进行字节序转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*./inet/htons.c */
#undef htons
#undef ntohs

uint16_t
htons (x)
     uint16_t x;
{
#if BYTE_ORDER == BIG_ENDIAN
  return x;
#elif BYTE_ORDER == LITTLE_ENDIAN
  return __bswap_16 (x);
#else
# error "What kind of system is this?"
#endif
}
weak_alias (htons, ntohs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* ./sysdeps/x86/bits/byteswap-16.h */
#ifdef __GNUC__
# if __GNUC__ >= 2
#  define __bswap_16(x) \
     (__extension__                                  \
      ({ register unsigned short int __v, __x = (unsigned short int) (x);     \
     if (__builtin_constant_p (__x))                      \
       __v = __bswap_constant_16 (__x);                      \
     else                                      \
       __asm__ ("rorw $8, %w0"                          \
            : "=r" (__v)                          \
            : "0" (__x)                              \
            : "cc");                              \
     __v; }))
# else
/* This is better than nothing.  */
#  define __bswap_16(x) \
     (__extension__                                  \
      ({ register unsigned short int __x = (unsigned short int) (x);          \
     __bswap_constant_16 (__x); }))
# endif
#else
static __inline unsigned short int
__bswap_16 (unsigned short int __bsx) {
  return __bswap_constant_16 (__bsx);
}
#endif

/* Swap bytes in 16 bit value.  */
#define __bswap_constant_16(x) \
     ((unsigned short int) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)))


3. C 语言实现大小端判断

  • 判断大小端。
1
2
3
4
5
6
int little = 1;
if (*(char*)(&little) == 0) {
    printf("big endian\n");
} else {
    printf("little endian\n");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

unsigned char is_little_endian() {
    static int little = 1;
    return (*(char*)(&little) == 1);
}

void swap(void* data, int n) {
    if (is_little_endian()) return;

    int i;
    unsigned char *p, temp;

    p = (unsigned char*)data;
    for (i = 0; i < n / 2; i++) {
        temp = p[i];
        p[i] = p[n - 1 - i];
        p[n - 1 - i] = temp;
    }
}

int main(int argc, char** argv) {
    int a = 0x12345678;
    swap(&a, sizeof(a));
    printf("a: %x\n", a);
    return 0;
}

4. 参考

  • 《UNIX 网络编程》

作者公众号
微信公众号,干货持续更新~