文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。
《嵌入式Linux与物联网软件开发:C语言内核深度解析》目录
以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。
本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
第一章 C语言与内存
程序=数据+算法
内存
作用:存储可变数据(全局、局部变量)。
变量就是在内存中分配一块内存空间,将它的地址和变量名相关联。
/*定义一个变量a,以0x0061ff1c开始的四个字节的地址存放a的值*/
int a=0;
printf("size of a:%d\n",sizeof(a)); //4
printf("address of a:%p\n",&a); //0061ff1c
分类:SRAM、DRAM(SDRAM、DDR1、DDR2...LPDDR)
数据和代码存放方式
冯.诺依曼:数据和代码放一起
哈佛结构:数据和代码分开存储
数据结构
数据结构研究数据如何组织并如何在内存中存放
如何管理内存
汇编:操作内存时直接使用内存地址
C语言:编译器帮我们管理内存,我们通过编译器提供的变量名来访问内存。需要用到大块内存时,有OS时通过API(malloc free)来访问系统内存;无OS时自己定义数组、结构体等来解决
c++:new对象(为对象分配内存),使用完delete对象(释放内存)。如果使用完忘记delete,对象占用的内存就无法释放,也就是内存泄露
Java、C#、python等:通过虚拟机来操作内存
内存本质
从逻辑角度看,内存是很多个内存单元格组成,单元格存放数据,每个单元格有一个固定的地址
通俗讲,内存(主存、显存等)可看做一栋大楼,大楼里每个房间都有门牌号,房间里住着的人就是数据,我们要去访问某个人,要知道他住哪一层哪一个房间
同理CPU操作内存过程大致如下
读写控制总线控制对内存进行读或写的操作
通过地址总线访问内存
通过数据总线传输数据
bit、byte、word等
bit:0、1
byte:8bit
word:一般说32位系统,一个word对应32bit。不同的系统占据的空间不同,不需要死记,C语言用sizeof即可,用法如下:
#include <stdio.h>
#define N 5
void main()
{
int array[N]={1,2,3,4,5};
printf("\t sizeof(char)=%d \n",sizeof(char));
printf("\t sizeof(int)=%d \n",sizeof(int));
printf("\t sizeof(short)=%d \n",sizeof(short));
printf("\t sizeof(long)=%d \n",sizeof(long));
printf("\t sizeof(unsigned int)=%d \n",sizeof(unsigned int));
printf("\t sizeof(unsigned short)=%d \n",sizeof(unsigned short));
printf("\t sizeof(unsigned long)=%d \n",sizeof(unsigned long));
printf("\t sizeof(float)=%d \n",sizeof(float));
printf("\t sizeof(double)=%d \n",sizeof(double));
printf("\t sizeof(array)=%d \n",sizeof(array));
printf("\t sizeof(array)/N=%d \n",sizeof(array)/N);
printf("\t sizeof(array)/sizeof(array[0])=%d \n",sizeof(array)/sizeof(array[0]));
}
/*
sizeof(char)=1
sizeof(int)=4
sizeof(short)=2
sizeof(long)=4
sizeof(unsigned int)=4
sizeof(unsigned short)=2
sizeof(unsigned long)=4
sizeof(float)=4
sizeof(double)=8
sizeof(array)=20
sizeof(array)/N=4
sizeof(array)/sizeof(array[0])=5
*/
函数名
C语言中,函数是一段代码的封装,函数名(指针)是这一段代码的首地址,有了函数名,就有了地址,才实现函数的调用
#include <stdio.h>
int add(int a,int b){
return a+b;
}
int main(void){
printf("%p\n",add); //00401400
printf("%p\n",*add); //00401400
printf("%p\n",&add); //00401400
return 0;
}
数组
数组和变量没有本质区别,只是解析方法不同。
数组就是在内存中开辟一块连续的内存空间,数组名就是内存空间的首地址
用指针来访问元素,每移动一个元素位置,实际上移动一个元素空间的大小(如下,(arr+1)比arr的地址多了4个字节)
int main(void){
int arr[5]={1,2,3,4,5};
printf("%d\n",sizeof(arr)); //20
printf("%d\n",sizeof(*arr)); //4
printf("%d\n",sizeof(arr)/sizeof(*arr)); //5
printf("%p\n",arr); //0061ff0c
printf("%p\n",&arr); //0061ff0c
printf("%p\n",&arr[0]); //0061ff0c
printf("%p\n",&arr[1]); //0061ff10
printf("%p\n",arr+1); //0061ff10
printf("%d\n",*arr); //1
printf("%d\n",arr[0]); //1
printf("%d\n",arr[1]); //2
printf("%d\n",*(arr+1)); //2
return 0;
}
数组优点:定义简单,方便访问
数组缺点:
数组中所有元素类型必须相同
数组大小必须在定义时给出
数组的空间必须是连续的(解决办法是使用链表)
结构体
结构体属于聚合数据类型,结构体变量在被定义后,编译器在编译时会为所有成员分配空间
结构体变量名代表的是整个结构体变量,不像数组名代表地址
#include <stdio.h>
typedef struct {
char id[8];
char name[8];
int age;
}Student;
int main(void){
Student stu={"2019","ckj",18};
printf("%p\n",&stu);
printf("%p\n",&stu.id);
printf("%p\n",&stu.id[0]);
printf("%p\n",&stu.id[1]);
printf("%p\n",&stu.age);
return 0;
}
向函数传递结构体时,实际上是传递结构体成员的值,但是如果定义的结构体是多成员结构或者有数组的结构时,把整个结构体变量进行压栈,运行性能会恶化。解决办法是向函数传递结构体指针,压栈的仅仅是结构体变量的地址
#include <stdio.h>
typedef struct {
char id[8];
char name[8];
int age;
}Student;
/*
函数addAge1被调用时,在函数栈中开辟stu1的变量空间
可看做复制stu给stu1,实际上操作的是stu1与main函数的stu无关
*/
Student addAge1(Student stu1){
stu1.age++;
return stu1;
}
/*
指针stu2指向stu
即在addAge2中直接操作变量stu
*/
void addAge2(Student* stu2){
(stu2->age)++;
}
int main(void){
Student stu={"2019","ckj",18};
addAge1(stu);
printf("%d\n",stu.age); //18
addAge2(&stu);
printf("%d\n",stu.age); //19
printf("%d\n",addAge1(stu).age); //20 仅仅把返回值输出而已,实际上stu.age还是19
printf("%d\n",stu.age); //19
return 0;
}
栈(自动管理区)
使用:函数内部的局部变量
定义:栈用来保存局部变量,保存函数调用所需的所有维护信息,栈是一种维护内存的机制。运行时空间自动分配,运行结束空间自动回收。
特点:FILO先进后出,栈指针的移动和内存分配是自动完成的,不需要程序参与。能够被反复使用。
约束:栈有大小,溢出危害很大,所以定义局部变量时不能太多或太大(如不能定义局部变量int a[10000])
临时性:函数不能返回栈变量的指针,这个空间在运行结束就会被释放。
堆(动态管理区)
使用:变量只在程序的一个阶段期间有用
定义:动态内存管理方式
特点:
容量不限,动态分配
申请(malloc)和释放(free)需要手工进行,不释放会存在内存泄露
#include <stdio.h>
#include <stdlib.h>
int main(void){
//一:申请1000个int型元素的数组
int *p=(int *)malloc(1000*sizeof(int));
//二:检验申请是否成功
if(p==NULL){
printf("malloc error \n");
return -1;
}
//三:使用申请到的内存
*(p+0)=1;
*(p+1)=2;
//四:释放
free(p);
return 0;
}
静态空间
编译器在编译程序时就已经确定了静态存储区的大小,程序运行结束才释放内存空间
代码区:存放指令代码,readOnly,(特殊数据段如 char *p="hello"; 字符串“hello”分配在代码段)
常量区:存放程序中用到的常量(const),readOnly
静态数据区:存放静态局部变量和全局变量,rw
第二章 C语言位操作
这一部分还是书里写得比较精彩,我只是总结我需要的part,读者请阅读原书或请跳过
位操作符与逻辑操作符
&按位与,&&逻辑与
|按位或,||逻辑或
~按位取反,!非
位异或^(两次异或可以取得原值)
a = 0 0 0 0 1 0 1 0;
b = 0 0 0 1 0 1 0 0;
第一次异或 a^b= 0 0 0 1 1 1 1 0;
第二次异或a^b^b= 0 0 0 0 1 0 1 0;
即a^b^b == a^(b^b)
因为一个数异或自己等于 0000 0000,而 0000 0000异或任何数字,结果是任何数字本身(两次异或可以取得原值,用在简单加密上)
位操作与寄存器
操作片内外设,就是操作片内外设的控制寄存器。控制硬件就是读写寄存器(寄存器也可理解为特定地址的内存)
对于32位嵌入式系统,寄存器是32位的,无法直接修改寄存器中的某一位,因此,修改寄存器的特定位,需要把32bit的数据整体读出,修改后再写入。即读-改-写
使用移位获得特定位为1的二进制数
bit3~bit7、bit23~bit25为1
int a = 0b 0000 0011 1000 0000 0000 0000 1111 1000; //此处空格应删去
int a = 0x038000f8;
int a = (0x1f<<3) | (0x07<<23);
使用移位获得特定位为0的二进制数
bit4~bit10为0
int a = 0b 1111 1111 1111 1111 1111 1011 1110 1111; //此处空格应删去
int a = 0xfffffbef;
int a = ~(0x7f<<4);
练习1
给定整型数a,清除a的bit15~23,保持其他位不变
a &= ~(0x1ff<<15);
练习2
给定整型数a,取出a的bit3~bit8
a &= (0x3f<<3);
a >> 3;
练习3
给寄存器a的bit7~bit17赋值937
(将bit7~bit17清零,再将937设置到bit7~bit17)
a &= ~(0x7ff<<7);
a |= (937<<7);
练习4
给寄存器a的bit7~bit17中的值加17
(直接 a += 0x00000f80 可能会溢出影响到其他位)
读出bit7~bit17
bit7~bit17加17
将bit7~bit17清零
将第二步算出的值加17
tmp = (a&(0x7ff<<7));
tmp >>= 7;
tmp += 17;
a &= ~(0x7ff<<7);.
a |= tmp<<7;
练习5
直接操作寄存器,点亮stm32的红灯(GPIOB_5输出低电平)
步骤
开启GPIOB所在的APB2的RCC时钟
配置GPIOB的CRL寄存器,设置GPIO模式
配置GPIOB的ODR寄存器,PB5输出低电平
/*define GPIOB_ODR = *(unsigned int*)(0x40010c0c);*/
//RCC_APB2ENR bit3置1,即GPIOB端口时钟使能
*(unsigned int*)(0x40021018) |= (1<<3);
//清空GPIOx_CRL 的bit20~bit23
*(unsigned int*)(0x40010c00) &= ~( 0x0F<< (4*5));
//向GPIOx_CRL 的bit20~bit23 写入3,即PB5以50MHz通用推挽输出
*(unsigned int*)(0x40010c00) |= (3<<4*5);
//GPIOx_ODR 的 bit5 置0,即PB5输出低电平
*(unsigned int*)(0x40010c0c) |= ~(0x20);