字节序转换关系
在进行网络编程的时候 经常会遇到这个问题, 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。
其实这里面有两个环节:
- (htons)客户端主机字节序转网络字节序。
- (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)