初出茅庐的小李第38篇博客之C语言按位逻辑运算符深入理解分析

什么是按位逻辑运算符?

我们知道在C语言中有位操作包括按位与、按位或、按位取反、按位异或、左移、右移等。
我们在嵌入式编程中往往会遇到操作寄存器的某一位使其控制某个功能(比如GPIO口的使能位),而且有的时候我们还需要查看某一位或者某几位来判断寄存器的状态。
这个时候移位和掩码就派上用场了。
需要注意的是:这些逻辑运算符(按位逻辑运算符)和我们C语言所讲的常规逻辑运算符是不一样的,常规逻辑运算符包括 (&& || !)常规的逻辑运算符针对的是整个数值。

按位求反(~)

它是一个一元运算符
可以把某一位的1变成0也可以把某一位的0变成1

按位与(&)

它是一个二元运算符
通过逐位比较两个运算对象,生成一个新值;
对于每个位,只有两个运算位都为真时结果才为真;

按位或(|)

它是一个二元运算符
通过逐位比较两个运算对象,生成一个新值;
对于每个位,只要有一个为真结果就为真;

按位异或(^)

它是一个二元运算符
通过逐位比较两个运算对象,对于每个位相同则这两位一伙的结果为0,这里相同包括两个1和两个0,相异为1这里的相异是指两个有一个是1另外一个是0;

掩码

所谓掩码就是指可以设置某一位或者某几位为真或者假的数据;它长用到的就是按位与运算如果我们想要置位某一位比如打开某一位我们可以设置一个掩码使我们想要打开的这一位设置成1其它位全部设置成0让我们想设置的这个数跟我们的掩码进行按位与运算就可以实现;因为1与数据是1结果就会变成如果数据是0结果就会变成0,0与数据不算是1还是0都会被掩码所屏蔽,而如果是1就会被显示。

掩码用法1(设置位)

/*使用|运算符任何数据和0或结果都是本身,任何数据和1或结果就是1*/
	printf("打开某几个位其它位保持不变\n");
	printf("flags=%0x\n"flags);
	printf("flags|=mask%0x\n"flags|mask);

掩码用法2(清空位)

/*使用&运算符任何数据和1与结果都是本身,任何数据和0与结果就是0*/
	/*关闭某几位的mask可以通过用打开某几位的mask按位取反得到想要的0*/
	printf("关闭某几个位其它位保持不变\n");
	printf("flags=%0x\n"flags);
	printf("flags&=~mask%0x\n"flags&=~mask);

掩码用法3(切换位)

/*使用^运算符任何数据和1与结果都是和之前状态相反,任何数据和0异或结果都是和之前数据相同*/
	/*切换某几个位是指打开已经处于关闭状态的位,或者关闭已经处于打开状态的位 */
	/*通过设置mask为1的位可以实现该位的切换,设置mask为0的为来包子吃该位的状态不变*/
	printf("切换某几个位其它位保持不变\n");
	printf("flags=%0x\n"flags);
	printf("flags^=mask%0x\n"flags^=mask);

掩码用法4(检查位的值)

/*使用掩码来检测某一位或者某几位的值*/
	printf("使用掩码来检测某位或者几位的值\n");
	if((flags & mask)==mask)
	{
		pintf("对应mask1位置全为真\n");
	}
	else
	{
		pintf("对应mask位置位至少有一位为假\n");
	}

实际写程序进行置位和清除操作采用的方法

由于掩码书写不是很灵活我们可以利用这种思想去简化我们的程序使其更具有一般性一般采用下面的方法来设置和清除某位

  • 设置某位
  • 清除某位
/*LED灯代码*/

	//GPM4CON[15:0]=[0000_0000_0000_0000];清空寄存器
    清除0-15位
	GPM4CON &=~((0xf<<4*3)|(0xf<<4*2)|(0xf<<4*1)|(0xf<<4*0));
   
	//GPM4CON[15:0]=[0001_0001_0001_0001];output模式
	设置第12位 第8位 第4位 第0位
    GPM4CON |=((0x1<<4*3)|(0x1<<4*2)|(0x1<<4*1)|(0x1<<4*0));

	//默认关闭所有LED灯

移位运算符1(左移<<)

左移运算符将其左面侧的运算对象每一位向左移动其右侧运算对象所指定的位数;左侧运算对象移出左末端位的值丢失,用0填充空出的位置
(10001010)<<2
00101000
改操作本身不改变其运算对象的值而是产生了一个新的值

移位运算符2(右移>>)

右移运算符将其左面侧的运算对象每一位向右移动其右侧运算对象所指定的位数;左侧运算对象移出右末端位的丢失。对于无符号类型,用0填充,或者符号位的副本填充(某些机器)
(10001010)>>2
00100010

移位运算符的用法

  1. 可以快速的乘除运算
  2. 获取较大单元的一些位数据(非常有用)

获取一个32位数据的前12位数据和后20位数据(linux内核设备号的获取)

这是一位博主写的分析很好借鉴一下
下面这段话出自linux内核linux/kdev.h中,是关于主次设备号操作的一些宏

#ifndef _LINUX_KDEV_T_H
#define _LINUX_KDEV_T_H
#ifdef KERNEL
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

对上面的这个宏要理解的话先得理解下面这段话先得理解1u<<(port)的含义。
1u<<(port)可以理解为取一个无符号数,其大小为1,并且将其左移port位。
假如port等于3,则其等同于:
1<<3;
在理解了以上的含义以后,可以发现 (1U << MINORBITS) 的结果是0000 0000 0001 0000 0000 0000 0000 0000
((1U << MINORBITS) - 1)的结果是0000 0000 0000 1111 1111 1111 1111 1111

**注意:这里-1相当于加上-1的补码(计算机里面负数的存储是以补码的形式存在的)我刚开始也没有看明白

-1的原码:1000 0000 0000 0001
-1的反码:1111 1111 1111 1110
-1的补码:1111 1111 1111 1111**

那么进一步分析可以知道MAJOR(dev)得到的是dev的高12位,MINOR(dev)得到的是dev的低20位
MKDEV(ma,mi) 就是先将主设备号左移20位,然后与此设备号相加得到设备号

差不多基本就是这些了,我们想要获取某些位的信息可以模仿内核的实现方法很巧妙

下面是我的测试代码

#include 
#include 
int main(void)
{
	unsigned char val1;
	unsigned char newval1;
	unsigned char vala,valb;
	unsigned char newval2;
	unsigned char newval3;
	unsigned char newval4;
	unsigned int ch = 0xcdbf;
	unsigned char mask = 0xE2;   /*11100010*/
	unsigned char flags =0x2D;   /*00101101*/
	
	
	printf("按位取反\n");
	val1= 2;
	newval1= ~val1;
	printf("val = %d\n",val1);
	printf("~val = %d\n",newval1);
	printf("*****************\n");
	printf("按位与\n");
	vala = 0x93;          /*10010011*/
	valb = 0x3D;          /*00111101*/
	newval2 = vala & valb;/*00010001*/
	vala &=valb;
	printf("newval2 = %d\n",newval2);
	printf("vala = %d\n",vala);
	printf("*****************\n");
	printf("按位或\n");
	vala = 0x93;          /*10010011*/
	valb = 0x3D;          /*00111101*/
	newval3 = vala | valb;/*10111111*/
	vala |=valb;
	printf("newval3 = vala | valb =%d\n",newval3);
	printf("vala = %d\n",vala);
	printf("*****************\n");
	printf("按位异或\n");
	vala = 0x93;          /*10010011*/
	valb = 0x3D;          /*00111101*/
	newval4 = vala ^ valb;/*10101110*/
	vala ^=valb;
	printf("newval4 = vala ^ valb=%d\n",newval4);
	printf("vala = %d\n",vala);
	printf("*****************\n");
	printf("掩码操作\n");
	printf("ch= %0x\n",ch);
	ch&=0xff;
	printf("ch&=0xff = %0x\n",ch);
	printf("*****************\n");
	/*使用|运算符任何数据和0或结果都是本身,任何数据和1或结果就是1*/
	printf("打开某几个位其它位保持不变\n");
	printf("flags=%d\n",flags);
	printf("flags|=mask=%d\n",flags|mask);
	printf("*****************\n");
	/*使用&运算符任何数据和1与结果都是本身,任何数据和0与结果就是0*/
	/*关闭某几位的mask可以通过用打开某几位的mask按位取反得到想要的0*/
	printf("关闭某几个位其它位保持不变\n");
	printf("flags=%d\n",flags);
	printf("flags&=~mask=%d\n",flags&=~mask);
	printf("*****************\n");
	/*使用^运算符任何数据和1与结果都是和之前状态相反,任何数据和0异或结果都是和之前数据相同*/
	/*切换某几个位是指打开已经处于关闭状态的位,或者关闭已经处于打开状态的位 */
	/*通过设置mask为1的位可以实现该位的切换,设置mask为0的为来包子吃该位的状态不变*/
	printf("切换某几个位其它位保持不变\n");
	printf("flags=%0d\n",flags);
	printf("flags^=mask=%d\n",flags^=mask);
	printf("*****************\n");
	/*使用掩码来检测某一位或者某几位的值*/
	printf("使用掩码来检测某位或者几位的值\n");
	if((flags & mask)==mask)
	{
		printf("对应mask1位置全为真\n");
	}
	else
	{
		printf("对应mask1位置位至少有一位为假\n");
	}
	printf("*****************\n");
	flags++;
	if((flags & mask)==mask)
	{
		printf("对应mask1位置全为真\n");
	}
	else
	{
		printf("对应mask1位置位至少有一位为假\n");
	}
	printf("*****************\n");
	return 0;
}

测试输出结果

aqst@host:~/study/004weiyunsuan$ ./001
按位取反
val = 2
~val = 253
*****************
按位与
newval2 = 17
vala = 17
*****************
按位或
newval3 = vala | valb =191
vala = 191
*****************
按位异或
newval4 = vala ^ valb=174
vala = 174
*****************
掩码操作
ch= cdbf
ch&=0xff = bf
*****************
打开某几个位其它位保持不变
flags=45
flags|=mask=239
*****************
关闭某几个位其它位保持不变
flags=45
flags&=~mask=13
*****************
切换某几个位其它位保持不变
flags=13
flags^=mask=239
*****************
使用掩码来检测某位或者几位的值
对应mask1位置全为真
*****************
对应mask1位置位至少有一位为假
*****************
aqst@host:~/study/004weiyunsuan$ 

你可能感兴趣的:(笔记,linux,位运算,逻辑运算符)