嵌入式linux C语言(一)——位运算的使用

嵌入式linux C语言(一)——位运算的使用

ARM是内存与IO统一编址,SoC中有很多控制寄存器,通过对这些寄存器进行位运算对这些控制寄存器进行设置,进而控制外设功能。在修改寄存器某些位的过程中不能修改其他的位。

一、位运算基础

C语言基本的位操作符有与、或、异或、取反、左移、右移六种位运算符。如下表所示:


符号


描述


运算规则


&



两个位都为1时,结果才为1


|



两个位都为0时,结果才为0


^


异或


两个位相同为0,相异为1


~


取反


0变1,1变0


<<


左移


各二进位全部左移若干位,高位丢弃,低位补0


>>


右移


各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

位运算使用说明:

1、六种位运算只能用于整型数据,对float和double类型进行位操作会被编译器报错。

2、逻辑运算与位运算的区别:逻辑运算是将参与运算的两个表达式整体的结果进行逻辑运算,而位运算是将参与运算的两个数据,按对应的二进制数逐位进行逻辑运算。逻辑运算符有逻辑与&&、逻辑或||、逻辑非!,位运算则有六种运算符,位与&、位或|、位异或^、位取反~、位左移<<、位右移>>。

3、如果左移位数>=类型长度,在GCC环境下,GCC编译器会报警告,但实际左移位数为左移位数%(8 * sizeof(int))。例如:

int i = 1, j = 0x80000000; //设int为32位

i = i << 33;   // 33 % 32 = 1 左移1位,i变成2

j = j << 33;   // 33 % 32 = 1 左移1位,j变成0,最高位被丢弃

4、在C语言中,左移是逻辑/算术左移(两者完全相同),右移是算术右移,会保持符号位不变。左移时总是移位和补零。右移时无符号数是移位和补零,此时称为逻辑右移;而有符号数大多数情况下是移位和补最左边的位(也就是补最高有效位),移几位就补几位,此时称为算术右移。 算术移位是相对于逻辑移位,它们在左移操作中都一样,低位补0即可,但在右移中逻辑移位的高位补0而算术移位的高位是补符号位。

右移对符号位的处理和左移不同,对于有符号整数来说,比如int类型,右移会保持符号位不变。符号位向右移动后,正数的话补0,负数补1,也就是汇编语言中的算术右移。当移动的位数超过类型的长度时,会取余数,然后移动余数个位。

int i = 0x80000000;

i = i >> 1;  //i的值不会变成0x40000000,而会变成0xc0000000

5、位操作符的运算优先级比较低,因为尽量使用括号来确保运算顺序,否则很可能会得到莫明其妙 的结果。比如要得到像1,3,5,9这些2^i+1的数字。写成int a = 1 << i + 1;是不对的,程序会先执行i + 1,再执行左移操作。应该写成int a = (1 << i) + 1。

二、位运算的使用

1、位与运算&

位与运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑与运算。典型应用如下:

A、特定数据段清零

快速对某一段数据单元的数据清零

unsigned int a = 0x00FF1278;

a &= 0xFFFF0FFF;//对a的bit12--bit15位进行清零,a=0x00FF0278

//a &= ~(0xF<<12) ;//对a的bit12--bit15位进行清零,a=0x00FF0278

B、保留数据区的特定位

unsigned int a = 0x00FF1278;

a &= (0xF<<12);//保留a的bit12--bit15位,其他清零,a=0x00001000

C、判断奇偶数

只要根据最未位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数

2、位或运算符|

位或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑或运算。典型应用如下:

A、对数据位置1

unsigned int a = 0x00FF0278;

a |= 0x0000F000;//对a的第12-15位置1,a=0x00FFF278

//a |= (0xF<<12);//对a的第12-15位置1,a=0x00FFF278

 

3、位异或^

位异或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑异或运算。只有当对应位的二进制数互斥的时候,对应位的结果才为真。典型应用如下:

A、特定位取反

设定一个数据的指定位,将1换为0,0换为1。例如整型数a=321,,将其低八位数据进行翻位的操作为a=a^0XFF。

B、数值交换

a=a^b;

b=b^a;

a=a^b;

不使用第三方变量可以用位操作来实现交换两数

4、位非~

位非运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑非运算。

A、变换符号

变换符号只需要取反后加1

5、位左移<<

左移运算的实质是将对应的数据的二进制值逐位左移若干位,并在空出的位置上填0,最高位溢出并舍弃。

6、位右移>>

位右移运算的实质是将对应的数据的二进制值逐位右移若干位,并舍弃出界的数字。如果当前的数为无符号数,高位补零。

如果当前的数据为有符号数,在进行右移的时候,根据符号位决定左边补0还是补1。如果符号位为0,则左边补0;但是如果符号位为1,则根据不同的计算机系统,可能有不同的处理方式。可以看出位右移运算,可以实现对除数为2的整除运算。

提示 将所有对2的整除运算转换为位移运算,可提高程序的运行效率。

A、求绝对值

int i = a >> 31;

return i == 0 ? a : (~a + 1);

int i = a >> 31;

return ((a ^ i) - i);

7、嵌入式开发中常用位操作

A、将寄存器指定位(第n位置为1

GPXX |= (1<<n)

GPXX |= (1<< 7) | (1<< 4 ) | (1<< 0);//第0、4、7位置1,其他保留

B、将寄存器指定位(第n位置为0

GPXX &= ~(1<<n )

将寄存器的第n位清0,而又不影响其它位的现有状态。

GPXX &= ~(1<<4 )

C、嵌入式开发位操作实例

unsigned int i = 0x00ff1234;

//i |= (0x1<<13);//bit13置1

//i |= (0xF<<4);//bit4-bit7置1

//i &= ~(1<<17);//清除bit17

//i &= ~(0x1f<<12);//清除bit12开始的5位

//取出bit3-bit8

//i &= (0x3F<<3);//保留bit3-bit8,其他位清零

//i >>= 3;//右移3位

//给寄存器的bit7-bit17赋值937

//i &= ~(0x7FF<<7);//bit7-bit17清零

//i |= (937<<7);//bit7-bit17赋值

//将寄存器bit7-bit17的值加17

// unsigned int a = i;//将a作为i的副本,避免i的其他位被修改

// a &= (0x7FF<<7);//取出bit7-bit17

//a >>= 7;//

// a += 17;//加17

// i &= ~(0x7FF<<7);//将i的bit7-bit17清零

// i |= (a<<7);//将+17后的数写入bit7-bit17,其他位不变

//给一个寄存器的bit7-bit17赋值937,同时给bit21-bit25赋值17

i &= ~((0x7FF<<7) | (0x1F<<21));//bit7-bit17、bit21-bit25清零

i |= ((937<<7) | (17<<21));//bit7-bit17、bit21-bit25赋值

三、位操作的宏定义

//用宏定义将32位数x的第n位(bit0为第1位)置位

#define SET_BIT_N(x,n) (x | (1U<<(n-1)))

//用宏定义将32位数x的第n位(bit0为第1位)清零

#define CLEAR_BIT_N(x,n) (x & (~(1U<<(n-1))))

//用宏定义将32位数x的第n位到第m位(bit0为第1位)置位

#define SET_BITS_N_M(x,n,m) (x | (((~0U)>>(32-(m-n+1)))<<(n-1)))

//用宏定义将32位数x的第n位到第m位(bit0为第1位)清零

#define CLEAR_BITS_N_M(x,n,m) (x & (~(((~0U)>>(32-(m-n+1)))<<(n-1))))

//用宏定义获取32位数x的第n位到第m位(bit0为第1位)的部分

#define GET_BITS_N_M(x,n,m) ((x & ~(~(0U)<<(m-n+1))<<(n-1))>>(n-1))

时间: 2024-08-27 09:21:54

嵌入式linux C语言(一)——位运算的使用的相关文章

嵌入式C语言之位运算 &amp;..|.~.&gt;&gt;

在嵌入式编程中,掌握位运算在操作寄存器的时候很方便,由于之前在上位运算的时候没上,但是由于位运算的难度不是很大,自己编写程序,顺便做些总结. &   |   - 这三个位运算符号不难理解,但是要区别与逻辑运算符号&&  和|| 1.需要总结的是:假如要使寄存器的值为1的话,一般用 这个寄存器的值来| 上1 比如要将i的值变为1则可以使用    i   |=  1;    意思就是将i的值与上1的值再给i.同理要让一个变量的值变成0的话,将使用 &上0     例如   i&

嵌入式 Linux C语言——C语言基础

嵌入式 Linux C语言--C语言基础 一.数据类型 1.基本数据类型 数据类型是创建变量的模型.变量名是连续存储空间的别名,程序中使用变量命名存储空间,通过变量可以使用存储空间.变量所占的内存大小取决于创建变量的数据类型. 2.有符号和无符号 有符号数中数据类型的最高位用于标识数据的符号,最高位为1表示为负数,最高位为0表示为正数. 计算机中有符号数通常使用补码表示,正数的补码为正数本身,负数的补码为负数的绝对值的各位取反后加1. 计算机中无符号数通常使用原码表示,无符号数默认为正数,没有符

嵌入式 Linux C语言(九)——C语言的安全问题和指针陷阱

嵌入式 Linux C语言(九)--C语言的安全问题和指针陷阱 C语言是灵活度和自由度较大的编程语言,作为C语言核心的指针更是让C语言程序员可以越过安全的栅栏,对某些内存区域进行破坏性访问,引发安全风险.很多安全问题都能追根溯源到指针的误用.本文将从指针的角度解读C语言常见的安全问题和指针陷阱. 一.指针的声明和初始化 1.不恰当的指针声明 int* ptr1, ptr2;//声明ptr1为int指针,ptr2为整型 int *ptr1, *ptr2;//ptr1,ptr2都声明为指针 #def

嵌入式Linux C语言(二)——指针

嵌入式Linux C语言(二)--指针 指针是C语言中广泛使用的一种数据类型,是C语言的灵魂.指针提供了动态操控内存的机制,强化了对数据结构的支持,而且实现了访问硬件的功能.学习指针是学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志. 一.指针的概念 在计算机中,所有的数据都是存放在内存中的,一般把内存中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不一样,如int占用4个字 节,char占用1个字节.为了正确地访问内存单元,必须为每个内存单元编上号.

嵌入式linux C++语言(四)——类与对象

嵌入式linux C++语言(四)--类与对象 类的设计和使用如下: #include <iostream>#include <stdlib.h>#include <stdio.h>#include <string.h>using namespace std;class Stack{public:    Stack(int size=1024);    ~Stack();    void init();    bool isEmpty();    bool

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

嵌入式linux C++语言(一)——C++简介

嵌入式linux C++语言(一)--C++简介 一.C++简介 C语言作是结构化和模块化的语言,适合处理较小规模的程序.对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言并不合适.为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming)思想,支持面向对象的程序设计语言应运而生.Smalltalk 就是当时问世的一种面向对象的语言.在实践工作中,由于C语言的广泛使用,在C语言的基础上根据面向对象的思想发展了C语言,形成了C

嵌入式Linux C语言(三)——指针与函数

嵌入式Linux C语言(三)--指针与函数 指针对函数的功能有巨大的贡献,指针能够将数据传递给函数,并且允许函数对数据进行修改.指针对于函数的作用主要有两方面:将指针传递给函数和声明函数指针. 一.程序的栈和堆 程序的栈和堆是C语言程序运行的运行时元素. 1.程序栈 程序栈是支持函数执行的内存区域,通常和堆共享一块内存区域,通常程序栈占据内存区域的下部,堆用内存区域的上部.程序栈存放栈帧,栈帧存放函数参数和局部变量.调用函数时,函数的栈帧被推倒栈上,栈向上长出一个栈帧,当函数终止时,函数的栈帧

嵌入式linux C++语言(六)——运算符重载

嵌入式linux C++语言(六)--运算符重载 运算符重载的本质是函数重载. 一.重载基础 1.运算符重载的语法 返值类型 operator 运算符名称(形参表列){    重载实体;} 2.友元重载 可以将运算符重载函数声明位友元函数 #include <iostream> using namespace std; class Complex { public:     Complex(float x=0, float y=0)         :_x(x),_y(y){}     voi