大小端

当一个数据在内存中占多个字节时,才会考虑大小端。内存地址是以字节为单位的,如果一个数据只占一个字节,比如char型的字符,那这个字节的内存中就保存字符对应的ascii码值。如果int型的整数占四个字节,那这四个字节分别保存这个整数的哪些部分呢,这就涉及到大小端了。(此外一个int数组中各个元素的地址是递增四个字节的,不要和每个元素内部怎么占用这四个字节的混淆。)

所谓的大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
所谓的小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令 )进行大小端的切换。

在几乎所有的机器上,多字节对象都被存储为连续的字节序列。
例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100。且x的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。

大端字节序:在内存中,低地址存放数据的高位,高地址存放数据的低位
小端字节序:在内存中,低地址存放数据的低位,高地址存放数据的高位

假设变量x类型为int,位于地址0x100处,它的十六进制为0x01234567,地址范围为0x100~0x103字节,其内部排列顺序依赖于机器的类型。
大端法从首位开始是:0x100: 01, 0x101: 23, 0x102: 45, 0x103: 67
小端法从首位开始是:0x100: 67, 0x101: 45, 0x102: 23, 0x103: 01
------------------------大端小端测试
#include <stdio.h>
int main(void)
{
int data = 0x12345678;
int i;
for(i=0; i<4; i++)
{
//注意这里要把int型的指针强转成char型,要不int*+1地址会加4.
printf("%#x ----->%p\n",*((char *)&data + i),(char *)&data + i);
}
return 0;
}
-------------------------运行结果可以看出我的测试机是小端:
0x78 ----->0x7fff9a2bbb68
0x56 ----->0x7fff9a2bbb69
0x34 ----->0x7fff9a2bbb6a
0x12 ----->0x7fff9a2bbb6b

bool LittleEndian()
{
int d = 0x12345678;
char* p = (char)&d;
return (*p == 0x78);
}

网络字节序
由于不同的系统会有不同的模式,为了统一,规定在网络传输中使用大端模式,这就是网络字节序。现在看看下面这四个函数的作用

uint32_t htonl(uint32_t hostlong);//32位的主机字节序转换到网络字节序
uint16_t htons(uint16_t hostshort);//16位的主机字节序转换到网络字节序
uint32_t ntohl(uint32_t netlong);//32位的网络字节序转换到主机字节序
uint16_t ntohs(uint16_t netshort);//16位的网络字节序转换到主机字节序
拿htonl和ntohl来分析,htonl函数的内部实现原理是这样,先判断主机是什么模式存储,如果是大端模式,就跟网络字节序一致,直接返回参数即可,如果是小端模式,则把形参转换成大端模式存储在一个临时参数内,再把临时参数返回;而ntohl函数的实现原理也是一样的过程,但是要注意它的参数,参数是网络字节序,就是大端模式存储,而不管你传入实参的过程是如果存储的,因此当判断主机是大端模式的时候,会直接返回,因为该函数默认会认为形参是网络字节序,把它当大端模式来看,如果判断主机是小端模式,就会将实参做转换,转换的过程并不复杂,就是逆序存储各个字节的数据,所以结果就被转换。

说到这里,可以看出一个规律来,就是如果主机与网络字节序不一致(也就是小端模式),这四个函数的返回值与传递进去的实参值的字节排序肯定是逆序的,所以返回值绝对不等于实参值,例如htonl(1)的结果肯定不是1,而如果主机与网络字节序一致(也就是大端模式),则这四个函数根本就没有做转换操作,而是直接返回实参值,这样他们的返回结果就肯定与实参值相同,即htonl(1)的结果是1。

这样,我们就得到了一个非常简便的判断系统是什么模式的方法,就是直接利用这四个函数来判断,如

if (1 != htonl(1)) {
//小端模式,作相应处理
} else {
//大端模式,作相应处理
}
或者直接用一个判断if(1 != htonl(1)),只有主机字节序与网络字节序不一致时,才调用那些函数去转换,否则不需要处理,这样可以减少多余的函数调用。

再来考虑这个问题:
网络上的数据流是字节流,比如一个int整数,在进行网络传输的时候,当接收端收到第一个字节的时候,它是将这个字节作为高位还是低位来处理呢?
在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化,网络传输一般采用大端序,也被称之为网络字节序,或网络序,IP协议中定义大端序为网络字节序。

高位在前说明“字节序”是’大端’。
就是各个字节上的各个bit代表的数据的数位是从高到低。
以普通数字举例,
123,代表一百二十三,就是高位在前的大端数
如果它代表是三百二十一,就是高位在尾的小端数
8个字节,第1个字节代表的是数据的最高8个bit,即第56到63位。
第2个字节代表第48-55bit,…第8个字节代表第0-7位;

大端机和小端机
  不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序。
  最常见的有两种:
    1. Little-endian:将低序字节存储在起始地址(低位编址)
    2. Big-endian:将高序字节存储在起始地址(高位编址)

小端机 LE(little-endian):
    最符合人的思维的字节序
    地址低位存储值的低位,地址高位存储值的高位
    怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说,低位值小,就应该放在内存地址小的地方,也即内存地址低位;反之,高位值就应该放在内存地址大的地方,也即内存地址高位

大端机 BE(big-endian):
    最直观的字节序
    地址低位存储值的高位,地址高位存储值的低位
    为什么说直观,不要考虑对应关系,只需要把内存地址从左到右按照由低到高的顺序写出,把值按照通常的高位到低位的顺序写出,两者对照,一个字节一个字节的填充进去

例子:在内存中双字0x01020304(DWORD)的存储方式。
    内存地址 4000 4001 4002 4003
    LE 04 03 02 01
    BE 01 02 03 04
  注:每个地址存1个字节,每个字有4个字节。2位16进制数是1个字节(0xFF=11111111)。

例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
        big-endian little-endian
    0x0000 0x12 0xcd
    0x0001 0x23 0xab
    0x0002 0xab 0x34
    0x0003 0xcd 0x12

x86系列的CPU都是little-endian的字节序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码猿杂谈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值