大端小端问题:

字节序转换关系

在进行网络编程的时候 经常会遇到这个问题, htons(), htonl() ,ntohs() ,ntohl()。这些函数会设计到大端小端问题 ,因为这个问题 ,我翻墙查了一下 端endian。

端(endian)的起源:

endian”一词来源于十八世纪爱尔兰作家乔纳森·斯威夫特(Jonathan Swift)的小说《格列佛游记》(Gulliver’s Travels)。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派(Big-Endians)”和“小端派(Little-Endians)”。以下是1726年关于大小端之争历史的描述:

我下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。

​ ——《格列夫游记》 第一卷第4章 蒋剑锋(译)

1980年,丹尼·科恩(Danny Cohen),一位网络协议的早期开发者,在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而第一次引用了该词。

Q1:为啥会有字节序的大端小端问题?
一个字节序,1Byte, 8bit 。正好存放在内存中的某一个 地址。但是对于多字节 doule float int 等 ,这是多字节的,就是一个这样类型的数据在一个内存地址放不下。需要多个内存地址进行存储。
Q2: 0x1234 在大端怎么进行存储 ,在小端怎么进行存储 。
   想先 0x1234 怎么理解 ,需要转换成 二进制。这里有个转换二进制的技巧。
  你看啊 :
     0x1234=116^3 + 216 ^2 +316 ^1 + 416 ^0
=1* (24)3 + 2*(24)2 + 3* (24)1 + 416^0
=1
2^12 + 22 ^8 + (1+2 ) 2 ^4 +4 *2 ^0
乘以 2 ^n 相当于 把前面那个数左移 n位置;
1 0001 0011 0100 (这个怎么形成的自己左移)

                               假设 0x1234 是int 型 数据,占4个字节,
                               现在把    1  0001 0011  0100   转成 4个字节 (32 bit )

                              0     0    0       0     0     0       0      0      0       0      0     0     0    0     0   0   0     0     0     0      1     0   0      1  0  0    0    0    1   1     0    1    0   0
                              31  30  29    28    27    26    25    24    23    22     21    20    19   18  17  16    15    14    13   12    11  10    9   8    7     6     5    4     3    2    1    0 
                                  

              下面展示 内存布局
                                    栈底(高地址)                                                                                             栈顶(低地址)
                  内存地址    0x104                                 0x103              0x102                  0x101
                                    0    0    1   1     0    1    0   0              0001  0001      00000000      00000000
                                    0x34                       0x12       0x0           ox0
                                    
          大端:低地址的(0x101) 存高位(31  30  29    28    27    26    25    24    23    22     21    20    19   18  17  16    15    14    13   12    11  10    9   8    7     6     5    4     3    2    1    0 )
              0x001234 =0x1234  
              这里的高位是指: 0x1234 这4个字节的数据在 32bit中的位置。

小端:正好反过来  ,此处省略。

网络字节序。

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

字节序关系

  • 字节序转换函数。

#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);
  • 转换流程。

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

    其实这里面有两个环节:

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

在这里插入图片描述

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

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

  • /*./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)
    
/* ./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)))
     
     注意: 大家可以试一试这个方法;

C 语言实现大小端判断

  • 判断大小端。

int little = 1;
if (*(char*)(&little) == 0) {
    printf("big endian\n");
} else {
    printf("little endian\n");
}

  0x100 0x101 0x102 0x103
  0      0      0     1
  
  
  这就是大小端 指针强转问题 ,有兴趣的话大家可以 自己写一个大小端 交换的程序试一试。提示:swap(Add1,Add3)
  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值