查看原文
其他

【C进阶】嵌入式开发中"移位操作"可要注意了!

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

1、聊一聊

    生活中每个人都会遇到一些烦心事,其实往往一首好的歌曲就能够把你从一大堆烦心事中拉出来,所以这里推荐一首让人听了以后可以放松去笑的好歌。

    好了,今天主要是跟大家说说C语言中移位运算的一些坑,同时也会比较全面的简述一下该部分内容,了解的小伙伴也可以温习一下。

2、算数移位与逻辑移位

    在进行嵌入式开发中移位操作算是大家用得比较频繁的一个操作符,因为大部分外设寄存器的每个位一般都代表着一项功能的使能或者选择,这样我们需要对一个或者多个置位或者清零等就会考虑使用移位操作,如下定义的宏大家应该不陌生。


1//公众号:最后一个bug
2# define SET_BIT(x,n) (x|(1<<(n-1)))
3# define CLEAR_BIT(x,n) (x&~(1<<(n-1)))

    那么大家在使用这两个宏的时候可能对于x变量的属性并没有太多的关注,因为大部分的外设寄存器都是用的无符号整形来表示的,如果是有符号整形呢?或者是浮点呢?最后的结果是这样的呢?

1逻辑移位

    逻辑移位运算简单的说就是不考虑符号位的移位处理,左移低位补0,右移高位补0,仅仅只是一种逻辑上的移动,所以对于无符号类型一般属于逻辑移位。


2算术移位

    算数移位运算与逻辑移位的区别是其会考虑算数数值问题,所以会考虑符号位的处理,一般算数移位仅仅只针对有符号整形,其左移采用逻辑移位高位移出,低位补零,而右移则是高位用符号位填充,低位移除。


    看到这里一些小伙伴该疑惑了,为什么逻辑左移和算数左移是一样的呢 ? 难道不是算数左移也保留符号位吗?又或者说有符号数到底使用的是逻辑左移还是逻辑右移?下面我将为大家一一解答。


3、回答上面的问题

    看首先为了回答第三节小伙伴的疑问,作者准备直接编写代码看汇编:(见汇编见真相),代码比较简单,我就直接上图了:


简单分析一下:
  • 直接拿着有符号和无符号整形数进行左右移位,对应着右侧作者特意高亮的汇编移位语句,大家应该发现第一行和第三行是一样的汇编移位语句,而且均是逻辑左移,但是我们的C代码一个是有符号一个是无符号。
  • 所以这里小小总结一下,这里只针对C语言的整形变量,无符号数移动均为逻辑移位,有符号数左移为逻辑移位,右移为算数移位

4、有符号左移溢出问题

    上一小节我们说了有符号数左移竟然是逻辑移位,那这问题可就大了,如果一个正数左移就有可能出现负数,先不慌,我敲下代码看看。

参考Demo:
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 char sVal1 = 64;
5 char sVal2 = -7;
6
7 /********************************
8  * Fuciton: 测量移位
9  * Author : (公众号:最后一个bug) 
10 *******************************/

11int main(int argc, char *argv[]) {
12
13    printf("sVal1    = %d\n",sVal1); 
14    sVal1  = sVal1<<1
15    printf("sVal1<<1 = %d\n",sVal1); 
16
17    printf("sVal2    = %d\n",sVal2); 
18    sVal2  = sVal2<<1
19    printf("sVal2<<1 = %d\n",sVal2); 
20
21    sVal2  = sVal2<<4
22    printf("sVal2<<4 = %d\n",sVal2); 
23
24    printf("\n公众号:最后一个bug\n");
25    return 0;
26}
运行结果如下图:

分析一下:
  • 从上面的结果可以看出,左移运算并不是与*2等价的,其存在溢出问题,由于符号位会被低位代替,所以其最终的符号由所移动的最后一位决定,大家在使用的时候需要注意。


5、浮点运算的移位问题

    我们对浮点数一般都不进行移位操作,并且大部分编译都是禁止该类语法操作,因为浮点存储格式中具体的bit段是有具体含义的,并且控制在固定的bit上(可以参考:【典藏】别怪"浮点数"太坑(C语言版本)),比如你把阶码移动到了尾码,那么这个移位操作的结果代表什么意义?根本无法理解,不过有些小伙伴为了能够获得更高的效率,想直接通过移位来代替*2运算,会考虑直接操作阶码来进行处理,可以参考如下代码:

硬核代码:
1#include <stdio.h>
2#include <stdlib.h>
3
4float fVal = 3.123;
5double dVal = 3.123;
6
7//单精度浮点移位 
8#define  FLOAT_SFT(f ,n )    ({ \
9                            int itemp = *(int*)&f;\
10                            itemp += n * 0x00800000u; \
11                            *(float*)&itemp;})
12
13//双精度浮点移位                            
14#define  DOUBLE_SFT(d ,n )    ({ \
15                            long long lltemp = *(long long*)&d;\
16                            lltemp += n * 0x0010000000000000LL;\
17                            *(double*)&lltemp;})                    
18
19/********************************
20 * Fuciton: 测量移位
21 * Author : (公众号:最后一个bug) 
22 *******************************/

23int main(int argc, char *argv[]) {
24
25    printf("FLOAT_SFT(fVal ,1)   = %f\n",FLOAT_SFT(fVal ,1));
26    printf("FLOAT_SFT(fVal ,-1)  = %f\n",FLOAT_SFT(fVal ,-1));
27
28    printf("DOUBLE_SFT(dVal ,1)  = %4.6f\n",DOUBLE_SFT(dVal ,1));
29    printf("DOUBLE_SFT(dVal ,-1) = %4.6f\n",DOUBLE_SFT(dVal ,-1));
30
31    printf("\n公众号:最后一个bug\n");
32    return 0;
33}

运行结果如下:


简单分析一下:
  • 上面的代码应该还是非常有意思的,可以真正体会到浮点数据的存储机构并且区分float类型和double类型的存储区别;
  • 同时使用指针对数据进行转化也得到了非常好的应用,特别是大家在以后通信等字节传输过程中需要使用到拆字节传输,也能够在这个实例中受益。


6、最后小结

    本篇文章其实主要是一些细节操作,大家在平时的编程中其实留给心眼就好了,对于一般的应用尽量不要使用有符号进行移位,一般都是转化为无符号移位处理,如果是为了增加*/2的效率,一定要注意移位的范围和溢出问题。

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

推荐好文  点击蓝色字体即可跳转

【Get】Linux下一起玩玩C程序设计

【√】以后复位芯片,数据再也不会丢了(实战篇)

【必看】嵌入式Engineer必经之路 -- "同步问题"

【重磅】剖析MCU的IAP升级软件设计(设计思路篇)

【进阶】除数为0,程序会奔溃吗?

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

GUI必备知识之“告别”乱码(浅显易懂)

【经典】把脉printf中的C进阶技巧

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

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

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