内存与c语言位操作

it2023-09-11  66

内存

1.为什么需要内存

内存是用来存储可变数据的,数据在程序中表现为全局变量、局部变量等(在gcc中,其实常量也是存储在内存中的)(大部分单片机中,常量是存储在flash中的,也就是在代码段),对我们写程序来说非常重要对程序运行更是本质相关。

先从操作系统角度讲:操作系统掌握所有的硬件内存,因为内存很大,所以操作系统把内存分成1个1个的页面(其实就是一块,一般是4KB),然后以页面为单位来管理。页面内用更细小的方式来以字节为单位管理。操作系统内存管理的原理非常麻烦、非常复杂、非常不人性化。那么对我们这些使用操作系统的人来说,其实不需要了解这些细节。操作系统给我们提供了内存管理的一些接口,我们只需要用API即可管理内存。 譬如在C语言中使用ma11oc free这些接口来管理内存。 没有操作系统时;在没有操作系统(其实就是裸机程序)中,程序需要直接操作内存,编程者需要自己计算内存的使用和安推。如果編程者不小心把内存用错了,错误結果要自已承担再从语言角度来讲;不同的语言提供了不同的操作内存的接口。 譬如汇编:根本没有仼何内存管理,内存管理全靠程序员自己,汇编中操作内存时直接使用内存地址(譬如0xd0020010),非常麻烦;譬如c语言:C语言中编译器帮我们管理直接内存地址,我们都是通过编译器提供的变量名等来访问内存的,操作系统下如果需要大块内存,可以通过API(ma1loc free)来访问系统内存。

2.内存位宽、字节、字、半字 硬件角度讲:硬件内存实现本身是有位宽的,即有8位的内存条16位内存条等;但是实际上硬件都是32位的所以都是按照32位硬件的特性和限制来的。

所有计算机所有机器中(不论32还是64位系统)位永远是1bit,字节永远是8bit。 注意:在Linux+arm这个软硬件平台字都是32位的。 3.数据类型和内存关系

数据类型是用来定义变量的,而这些变量需要存储、运算在内存中。所以数据类型必须和内存相匹配才能获得最好的性能,否则可能不工作或者效率低下。 32位系统中定义变量最好用int,因为这样效率高。原因就在于32位的系统本身配合内存等也是32位,这样的硬件配置天生适合定义32位的int类型变量,效率最高。也能定义8位的char类型变量或者16位的 short类型变量,但是实际上访问效率不高。

c语言位操作

一、位操作符 1.1 位与& 位与和逻辑与的区别:位与时两个操作数是按照二进制位彼次对应位相与的,逻辑与是两个操作数作为整体来相与的。 1.2位或 | 位或和逻辑或的区别;位或时两个操作数是按照二进制位彼次对应位相或的,逻辑或是两个操作数作为整体来相或的。 1.3位取反~ 注意:(1) c语言中位取反是~,c语言中的逻辑取反是!; (2)按位取反是将操作数的二进制位逐个按位反(1变成0,0变成1);而逻辑取反是真(在C语言中只要不是0的任何数都是真)变成假(在c语言中只有0表示假)、假变成真.

任何非0的数被按逻辑取反再取反就会得到1; 任何非0的数被按位取反再取反就会得到他自己;

#include<stdio.h> /* 按位取反再取反为本身 按逻辑取反 真变假 再取反假变真 (1》0 0》1) */ int main() { unsigned int a = 45,b,c; b = ~a; c = !a; printf("b=~a=%u\tc=!a=%u\n", b, c); b = ~~a; c = !!a; printf("b=~~a=%u\tc=!!a=%u\n", b, c); return 0; } 结果: b=~a=4294967250 c=!a=0 b=~~a=45 c=!!a=1

1.4位异或^ 2个数相等结果为0不等为1.

位与、位或、位异或的特点总结: 位与:(任何数,其实就是1或者0)与1位与无变化,与0位与变成0; 位或:(任何数,其实就是1或者0)与1位或变成1,与0位或无变化; 位异或:(任何数,其实就是1或者0)与1位异或会取反,与0位异或无变化

1.5左移位、右移位

C语言的移位要取决于数据类型 对于无符号数,左移时右侧补0(相当于逻辑移位) 对于无符号数,右移时左侧补0(相当于逻辑移位) 对于有符号数,左移时右侧补0(叫算术移位,相当于逻辑移位) 对于有符号数,右移时左侧补符号位(叫算术移位) 注意:嵌入式中都为无符号数

二、位与、位或、位异或在寄存器中特殊使用 2.2.1、寄存器操作的要求(特定位改变而不影响其他位) (1)ARM是内存与IO统一编址的,ARM中有很多内部外设,SoC中CPU通过向这些内部外设的寄存器写入一些特定的值来操控这个内部外设,进而操控硬件动作。所以可以说:读写寄存器就是操控硬件。 (2)寄存器的特点是按位进行规划和使用。但是寄存器的读写却是整体32位一起进行的(也就是说你只想修改bit5~bit7是不行的,必须整体32bit全部写入); 3)寄存器操作要求就是;在设定特定位时不能影响其他位; (4)方法是:读-改-写三部曲。读改写的操作理念,就是:当我想改变一个寄在器中某些特定位时,我不会直接去给他写,我会先读出寄存器整体原来的值,然后在这个基础上修改我想要修改的特定位,再将修改后的值整体写入寄存器。这样达到的效果是;在不影响其他位原来值的情况下,我关心的位的值已经被修改了。

特定位清零用& 如果希望将一个寄存器的某些特定位变成0而不影响其他位,可以构造一个合适的1和0组成的数和这个寄存器原来的值进行位与操作,就可以将特定位清零。 特定位置1用 | 操作手法:构造这样一个数:要置1的特定位为1,其他位为0,然后将这个数与原来的数进行位或即可。 特定位取反用^ 操作手法:构造这样一个数:要取反的特定位为1,其他位为0,然后将这个数与原来的数进行位或即可。

#include<stdio.h> int main() { unsigned int a = 0x123d0cd7, b=0xf0, c; c = a & b;//将一个寄存器值除了bit4~bit7外都清零 printf("c=0x%x\n", c); c = a| b;//将一个寄存器值的bit4~bit7置1,其他位不变 printf("c=0x%x\n",c); c = a ^ b;//将一个寄存器值的bit4~bit7取反,其他位不变 printf("c=0x%x\n", c); return 0; } 结果: c=0xd0 c=0x123d0cf7 c=0x123d0c27

三、用位运算构建特定二进制数 方法1:可以使用计算器等工具计算直接给完整的32位特定数;优点是可以用但是读程序时不容易理解; 方法2:写代码构建这个特定二进制数(主要是移位和取反)

使用移位获取特定位为1的二进制数 (1)最简单的就是用移位来获取一个特定位为1的二进制数。譬如我们需要一个bit3~bit7为1(隐含意思就是其他位全部为0)的二进制数,可以这样:(0x1f<<3) 解析:bit3-bit7是7-3+1=5所以就是11111即1f (2)更难一点的要求:取bit3bit7,同时bit23bit25为1,其余位为0的数: (0x1f<<3)|(7<<23))

int main() { unsigned int a; /* 下面表达式含义:有1个位或说明这个数字由2部分组成,第一部分中左移3位说明第一部分从bit3开始, 第一部分数字为0x1f说明这部分有5位,所以第一部分其实就是bit3到bit7;第二部分的解读方法同样的, 可知第二部分其实就是bit23到bit25所以两部分结合起来,这个数的特点就是:bit3~bit7和bit23~bi25为1,其余位全部为0 */ a = ((0x1f << 3) | (0x7 << 23)); printf("a=0x%x\n", a); return 0; } 结果: a=0x38000f8

再结合位取反获取特定位为0的二进制数 (1)比如要获取bit4~bi10为0,其余位全部为1的数。怎么做? 利用上面进的方法就可以:(0xf<<0)|(0x1ffff<<11) 但是问题是:连续为1的位数太多了,这个数字本身就很难构造,所以这种方法的优势损失掉了 所以这种特定位(比较少)为0而其余位(大部分)为1的数,不适合用很多个连续1左移的方式来构造,适合左移加位取反的方式来构造 思路是:先试图构造出这个数的位相反数,再取反得到这个数。(譬如本例中要构造的数bit4bit10为0其余位为1,那我们就先构造bit4bi10为1,其余位为0的数,然后对这个数按位取反即可)  ~(0x7F<<4)

> 总结:位与、位或结合特定二进制数即可完成寄存器位操作需求 > (1)如果你要的这个数比较少位为1,大部分位为0,则可以通过连续很多个1左移n位得到; > (2)如果你想要的数是比较少位为0,大部分位为1,则可以通过先构建其位反数,然后再位取反来得到; > (3)如果你想要的数中连续1(连续0)的部分不止1个,那么可以通过多段分别构造,然后再彼此位与即可。这时候因为参与位或运算的各个数为1的位是不重复的,所以这时候的位或其实相当于几个数的叠加。 注:要置1用|,用清零用&,要取反用^,~和<< >>用来构建特定二进制数. 位运算实战演练 1、给定一个整型数a,设置a的bit3,保证其他位不变。 a |= 1<<3 ; 2、给定一个整形数a,设置a的bit3~bit7,保持其他位不变 a |= 0x1f<<3 a |= 0b11111<<3 3、给定一个整型数a,清除a的bit15,保证其他位不变。 a &= ~(1<<15) 4、给定一个整形数a,清除a的bit15~bit23,保持其他位不变 a &= ~(0x1ff<<15) 5、给定一个整形数a,取出a的bit3~bit8 解析:先将除了bit3~bit8外清零再右移3位 a = (a & (0x3f<<3) )>>3 6、用c语言给一个寄存器的bit7~bit17赋值937(其余位不受影响) 解析:先清零bit7~bit17 再将937赋值后左移7位 a=a & (~(0x7ff<<7)); a |= (937<<7)  或者 a=a & (~(0x7ff<<7)) + (937<<7) 7、用C语言将一个寄存器的bit7~bit17中的值加17(其余位不受影响)。 解析:先读出bit7~bit17值再给其加17再将bit7~bit17清零再将算出值写入 temp = a & (0x3ff<<7); temp >>=7; temp += 17; a &= ~(0x3ff<<7); a |= temp<<7; 8、用c语言给一个寄存器的bit7~bit17赋值937,同时给bit21~bit25赋值17技术升级;用宏定义来完成位运算 a=a & (~(0x7ff<<7)) + (937<<7) a=a & (~(0x1f<<21)) + (17<<21) 或者: a=a & (~((0x7ff<<7) |(0x1f<<21))) ; a |= (937<<7) | (17<<21); 技术升级:用宏定义来完成位运算 1、直接用宏来置位、复位32位数的第n位(注:最右边为第1位)。 #define SET_NTH_BIT(x, n)(x |((1U)<<(n-1))) #define CLEAR_NTH_BIT(x, n)(x& ~((1U)<<(n-1))) 2、截取变量的部分连续位。例如:变量0x88,也就是10001000b,若截取第2~4位,则值为:100b=4 define GETBITS(x,n,m)((x&~(~(0U)<<(m-n+1))<<(n-1))>>(n-1)) 复杂宏分析: 先去掉外面括号 (x&~(~(0U)<<(m-n+1))<<(n-1)) >> (n-1) 分析为什么n-1 x & ~(~(0U)<<(m-n+1))<<(n-1) 分析为什么要& ~ (~(0U)<<(m-n+1)) << (n-1) 再分析: ~(0U)<<(m-n+1) //直接用宏来置位、复位32位数的第n位(注:最右边为第1位) #define SET_BIT_N(x,n) (x | (1U<<(n-1))) //置位 #define CLEART_BIT_N(x,n) (x & ~(1U<<(n-1))) //清零 //直接用宏来置位32位数的第n位到第m位(注:最右边为第1位,m为高位) /* 得到m-n+1个1 算法:先得到32位1 ~0U 再将第一步得到的数右移x位可得到m-n+1个1 (~0U)>>(32-(m-n+1) */ #define SET_BIT_N_M(x,n,m) (x | (((~0U)>>(32-(m-n+1))) <<(n-1))) int main() {`在这里插入代码片` unsigned int a = 0x0, b = 0; b = SET_BIT_N_M(a, 1, 4); printf("b=0x%x\n", b); return 0; } 结果: b=0xf
最新回复(0)