查看原文
其他

深度剖析"bit序"与"字节序"(追思永念)

bug菌 最后一个bug 2021-01-31

1、山河无恙,盼人间皆安 

"去时风雨锁寒江,归来落樱染轻裳"

"漫天飞花中 微笑望苍穹"

"山河无恙在我胸"

"愿君归来若春风"

"山河无恙 如初见模样"

    歌词中的每一句都触动着作者的内心,感谢那些为我们负重前行的英雄们,愿逝者安息,生者奋发,灾难止步,祖国昌盛!
    也希望今天的文章能够让更多的小伙伴受益,一起进步,一起加油!!

2、剖析字节序  

    1)对于字节序其实很多小伙伴应该都是非常熟悉了,平时大家也都叫它"大小端",不过也有一部分小伙伴可能只是简单的知道它的概念,并没有在实际的代码开发中去深入了解,作者这里详细解析一下这个问题,同时也为后面的bit序铺垫。

    2)既然叫"字节序",那么其描述的是字节的顺序问题,如果对于单个字节而言是没有字节序的概念的,然而对于多个字节的数据类型便会由于机器的差异在内存中的字节顺序排列也是存在不同的。那么我们在考虑代码的可移植性的时候就需要把其考虑进去!

    3)字节序也叫大小端模式,分别为:大端模式(Big-Endian)和小端模式(Little-Endian),简单的表述:大端模式-高字节存低地址;小端模式-高字节高地址;下面以四字节整形数据0x78563412为例子描述不同模式在内存中的分布情况:

    有些小伙伴很容易产生这样的误区:平时用指针移动好像并没有发现这样的问题,怎么上图指针移动到一下地址数据就有差别了呢?原因在于平时大家使用指针++的时候下一个地址为sizeof(指针类型)处,而非上图的地址+1。

    这里作者也编写了一个简单的程序供大家参考:

  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. /*****************************************

  4. * Fuction:main

  5. * Author :(公众号:最后一个bug)

  6. ****************************************/

  7. int main(int argc, char *argv[]) {

  8. printf("int_Size:%d\n",sizeof(int));

  9. int Val = 0x78563412;

  10. int *ptr_int = &Val;

  11. unsigned char *ptr_char = (unsigned char *)&Val;

  12. printf("ptr_int Addr:0X%X\n",ptr_int);

  13. printf("ptr_char Addr:0X%X\n",ptr_char);


  14. printf("ptr_char + 0:0X%X = 0X%X\n",ptr_char + 0,*(ptr_char + 0));

  15. printf("ptr_char + 1:0X%X = 0X%X\n",ptr_char + 1,*(ptr_char + 1));

  16. printf("ptr_char + 2:0X%X = 0X%X\n",ptr_char + 2,*(ptr_char + 2));

  17. printf("ptr_char + 3:0X%X = 0X%X\n",ptr_char + 3,*(ptr_char + 3));


  18. //用于判断字节序

  19. if((*(ptr_char))== 0x12)

  20. {

  21. printf("Little_Endian\n");

  22. }

  23. else

  24. {

  25. printf("Big_Endian\n");

  26. }

  27. printf("公众号:最后一个bug!");

  28. return 0;

  29. }

    下面是输出的结果,对于大端模式大家也可以参考上面的代码进行简单的测试。

    4)对于我们目前的PC机大部分都是X86架构,基本上都为小端模式。而在嵌入式中芯片架构各异,大家开发的时候特别是数据的存储与读取问题(比如说共联体数据的访问)都需要注意一下,对于大小端比较敏感的算法大家也可以通过大小端的特点编写对应转化算法进行对应的处理。


3、剖析bit序  

    1 )对于字节序大家应该算是比较熟悉,而对于bit序可能有些小伙伴可能关心的并不是很多,一方面一般的寻址最小单位都是byte,另一方面现在库都封装得太好了,基本上给大家的接口都是操作字节这个层面的事情,如果一旦我们要深入到更加原始的信号可能部分小伙伴还需要学习相关的知识。

    2)我们都知道大部分的通信都是采用"0101...”这样的电平bit信号来进行传输的,那我们从发送方到接受方到底高位bit(MSB)先传还是低位bit(LSB)先传,这个就需要我们进行bit序的定义的。

    3)比如说我们现在有需求需要用IO口模拟串口,或者IIC通信,那么我就势必需要去了解串口或者IIC通信的物理层数据发送格式,这时候的bit序就需要我们特别注意了,如下图所示UART通信的bit序和IIC的bit序刚好相反,这在我们进行模拟编程的时候是需要注意的。

    有些小伙伴会问,平时我都不进行模拟这些通信,底层都跟我做好了,基本上我获得byte来使用就行了,这个不了解也没关系。作者觉得吧,这些并不是很难的知识点,大家能够学习到肯定对以后会多一种解决问题的思路,比如说我现在和其他的设备通信不上或者是通信异常,那我们又该如何排除问题呢?

    4)对于一般的网络通信,底层都会把bit序跟大家转换好,不过也会存在一些情况需要大家进行相应的bit序的转化,具体根据实际情况进行确定。这里最后再分享一个平时自己在使用的bit序转化的算法:(如下图所示)

    算法参考代码如下:

  1. #include <stdio.h>

  2. #include <stdlib.h>

  3. /*****************************************

  4. * Fuction:Bit_Inverter

  5. * Author :(公众号:最后一个bug)

  6. ****************************************/

  7. unsigned char Bit_Inverter(unsigned char Val)

  8. {

  9. Val = ((Val & 0xAA) >> 1) | ((Val & 0x55) << 1);

  10. Val = ((Val & 0xCC) >> 2) | ((Val & 0x33) << 2);

  11. Val = ((Val & 0xF0) >> 4) | ((Val & 0x0F) << 4);

  12. return Val;

  13. }

  14. /*****************************************

  15. * Fuction:main

  16. * Author :(公众号:最后一个bug)

  17. ****************************************/

  18. int main(int argc, char *argv[]) {

  19. int i = 0;

  20. unsigned char test = 0x95;

  21. unsigned char str1[10] ={0};

  22. unsigned char str2[10] ={0};

  23. itoa(test, str1, 2);

  24. itoa(Bit_Inverter(test), str2, 2);

  25. printf("%s <---inv---> %s\n", str1,str2);

  26. printf("公众号:最后一个bug!");

  27. return 0;

  28. }

    运行结果如下所示:


4、最后小结  

    好了,今天的知识分享就在这里了,万丈高楼平地起,虽然这些小知识并不是那么高大上,不过个人经验来看:凡事都是一个量变到质变的过程,只要能够日积月累,必能厚积薄发

    这里是公众号:“最后一个bug”,一个为大家打造的技术知识提升基地。同时非常感谢各位小伙伴的支持,我们下期精彩见!

往期精彩文章

【硬壳】C程序里面嵌点"机器码"玩一玩"(小知识揭露大道理)

顿悟,神秘的register关键字(C语言篇) 

【典藏】深度剖析单片机程序的运行(C程序版) 

【连载】通过"库文件"学单片机驱动编程(5)-完结篇

☞ C语言为什么一般不在.h中定义函数或者变量?(精华)

手把手教你写Modbus-RTU协议(理论篇)

单片机开发之节省内存大法(C语言版本)

嵌入式编程之动态接口技术(经验干货)

【典藏】自制小型GUI界面框架(设计思想篇)

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存