系统的大小端

       网上已有很多关于大小端的文章,我这里再写一篇,虽然有重复,但目的是将自己的见解记录下来,并非无意义的复制粘贴。关于大小端的解释,百度百科大小端说的还算挺清楚的。当然,如果单看百度百科无法理解,可以多找几篇别人写的文章,看看别人是如何理解的,我是觉得多看几篇比单看一篇理解的清楚些。光看文章,以为自己理解了,那肯定是不行的,理论(看文字)结合实践(敲代码),理解才会更深刻。
       大小端的具体应用,我也不是很清楚,学习编程的过程中也没哪里要求要用到,不过掌握一下这个知识点并不碍事。要理解清楚大小端,首先弄清楚几个要点:

  • 字节的高低位

       比如有个十六进制数0x12345678(为什么用十六进制数举例?其实计算机只认识0和1,不管是字符还是数字,放到内存中后都是转换成一个二进制数进行储存,也就是一堆0和1。具体如何转换成二进制,和如何储存的,就涉及到另一个知识点了,这里不做过多解释),当然也可以用十进制数,如305419896(转换成十六进制后还是0x12345678…),二进制?当然行,再转换成十六进制嘛!不转换行不行?!额…应该行吧,不过我觉得会比较麻烦。我的理解就是:学过C语言的应该都知道,我们申请内存空间的时候都是我要malloc多少个字节(1字节等于8位),而不是我要malloc多少个位(比如我要malloc一个字节的空间,而不是我要malloc八位的空间);还有,描述变量类型的大小的时候都是:(32位系统下)char占1个字节、int占4个字节等等,而不是char占8位、int占32位。为什么用字节而不是位来描述呢?再讲下我的理解:据我所见,用来描述地址值的都是用十六进制,如0x00、0x01这两个地址(它们分别占1个字节,十六进制中,每两个符号位占一字节);如果用二进制应该也可以,那就可能这样,00000000、000000001(这两个地址也分别占一个字节),显然,用二进制进行描述就显得冗长,还是十六进制描述简单些。以上都是个人理解,是否完全正确,我也不确定,保留意见。综上两个解释,因此这里就用十六进制数来举例了。
       废话了一大堆,回归正题(这里也说明,要掌握一个知识点,肯定还会牵扯到更多其它知识点,并不是单单学一个就够了的)。这里再解释下,0x12345678这个数在内存中并不是占一个字节的,它在一段连续的内存中分别储存,可能0x12存在一个字节的内存地址中、0x34存在接下来的内存地址中,以此类推。那0x12345678这个数的高低字节怎么判断呢?学过数学的都知道从右到左的读个、十、百…这里的个位就是低位,十位相对于个位就是高位了。十六进制的高低字节也是如此0x78是低字节,0x12是高字节(低到高是从右到左)。这样描述就很清楚了吧,忘记了就念个十百吧。

  • 地址的高地位

       地址的高地位又如何判断呢?比如有一段连续的地址:0x00、0x01、0x02、0x03,很简单,值小的就低,大的就高,那就是0x00是低地址,往右递增。


       为什么要弄清上面讲的两个概念呢?那是因为:

  • 小端模式

       数据的低字节存放到内存的低地址中,高字节存到高地址中。并且小端模式下优先将数据填充到内存的低地址中。

  • 大端模式

       大与小正好相反。数据的低字节存放到内存的高地址中,高字节存到低地址中,且数据优先填充到高地址上。


       如果上面的概念还有点模糊,那就具体模拟下数据在大小端中的储存方式,数据0x12345678,一段连续地址0x00~0x03:

  • 小端模式储存

       地址:0x00 | 0x01 | 0x02 | 0x03       内存地址从左到右由低到高
       数据:0x78 | 0x56 | 0x34 | 0x12       数据字节从左到右由低到高

  • 大端模式储存

       地址:0x00 | 0x01 | 0x02 | 0x03       内存地址从左到右由低到高
       数据:0x12 | 0x34 | 0x56 | 0x78       数据字节从左到右由高到低

       这就是大小端储存数据的差异了。再说个简单的记忆方式:如果数据的实际情况与存在内存中的方式不一样,就是小端模式,一样的就是大端模式了(当然,不管是大端模式还是小端模式,同一个数据的值是不会变的)。大端模式适合人类理解,小端模式低对低,高对高,可能就适合计算机理解了。
       概念理解了,接下来怎么验证自己的系统是大端还是小端呢?当然是用代码了。上代码(解释都在代码中):

/*
 *  @endian_test.c
 *  @author:WangTaL
 *  @copyright:2017/1/13
*/
#include <stdio.h>

int main(int argc, char* argv[])
{
    // 学C语言的时候肯定学过联合体union,但是实际中用的很少,都是用结构体struct
    // 其实联合体可以用来测试大小端。联合体里头的所有变量都是共用同一块内存的!
    // 下面我们定义一个联合体变量uTest,并赋值其中的变量s,如码所示
    // 还定义了一个联合体变量uTest1,只不过赋值其中的变量n,如码所示
    // 其实定义一个就可以测试了,多一个只是反其道测试一下
    // 接下来就用printf()输出一下结果,如码所示
    union {
        int n;
        char s[4];
    } uTest = {.s = {0x12, 0x34, 0x56, 0x78}},
      uTest1 = {.n = 0x12345678};
    printf("uTest.n:0x%x\n", uTest.n);
    printf("uTest.s[0]:0x%x\n", uTest1.s[0]);

    // 不用联合体也可以。直接定义一个int型变量,然后定义一个char型
    // 变量储存到int型变量第一个字节的内容,由于char型变量占一个字节
    // 所以后面剩余字节的数据会丢失掉。最后当然是打印出来了,如码所示
    int n = 0x12345678;
    // 下面这句涉及到指针相关,意思就是取n的地址,将其转换为char*类型
    // 然后再通过*来取地址中的内容
    char s = *((char*)&n);
    printf("s:0x%x\n", s);

    // 再多说一个,其实用上面那两个就够了的
    // C语言中,指针类型是可以强制转换的,当然有一定的前提条件
    // 如果两个地址大小相等,也就是等量个字节数,就可以相互转换
    // 成另一种类型的指针.
    // s[4]是占4个字节,但是如果将n的地址强制转换并赋值给s是会
    // 报错的(大小不匹配).如果像上面那样,将n的第一个字节截取储存
    // 到s[0]中...但这不是我想要的结果,我要一个变量指向n的整个地址
    // 这就需要用到强大的结构体了,像下面这样,用struct封装s[4],并
    // 定义该类型的一个变量sTest,然后赋值,如码所示
    struct {
        char s[4];
    } sTest = {0x12, 0x34, 0x56, 0x78};
    // 下面这句的意思就是取sTest的地址,转换为int*类型
    // 再通过*取出其中的内容赋值给n
    n = *(int*)&sTest;
    printf("n:0x%x\n", n);

    // 再写几行代码解释下不同模式下数据优先填充到哪
    // 将n赋值为0x12,它占1个字节,而int占4个
    // 然后将n的地址中的内容赋值给sTest,并打印每个字节中的内容
    n = 0x12;
    // 这句可能看起来有些奇葩,其实知道原理的话也没什么奇葩
    // 先解释下意思,取n的地址,转换成sTest这个变量类型的指针
    // 再通过*取出其中的内容赋值给sTest
    // 简单解释下,因为sTest没有具体的名称,所以通过typeof(具体用法自己搜索)
    // 获取它的类型,加个*就表示这种类型的指针(如int*)
    sTest = *(typeof(sTest)*)&n;
    // 最后将sTest中4个字节中的内容都打印出来
    printf("sTest.s[0]:0x%x\n", sTest.s[0]);
    printf("sTest.s[1]:0x%x\n", sTest.s[1]);
    printf("sTest.s[2]:0x%x\n", sTest.s[2]);
    printf("sTest.s[3]:0x%x\n", sTest.s[3]);

    return 0;
}

       以上代码的运行结果:

这里写图片描述

       解释下结果(我的电脑是小端模式):

       第一行,我们给uTest.s的四个字节赋值了四个数(它们在内存中依次从低地址到高地址储存,0x12在低地址),然后通过uTest.n读取并打印,但是打印的时候需要转换成实际数据的,由于我的电脑是小端,低地址中的内容就放到低字节中所以打印结果就是0x78563412。

       第二行,结果之所以是0x78,是因为只截取了第一个字节的内容。

       第三行,原理同第二行。

       第四行,0x12在低地址,小端模式,转换后放在低字节,所以结果是0x78563412。

       剩下几行说明了小端模式下,数据优先填充到低地址中,sTest.s[0]的地址是低地址,它的内容是0x12,其余地址没有数据填充,所以都为0。


       到此结束。我觉得该记录的点都记在里面了,如果有人看后还有疑问或发现存在问题,欢迎提出。:)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值