文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。
以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。
本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com
第七章 存储类、作用域、生命周期、链接属性
存储类
存储类,就是存储类型。
内存被分为了栈、堆、数据段、bss段和text段等。
![](https://i-blog.csdnimg.cn/blog_migrate/a53f6521b9b1f5ca4a4f4475b2dde598.png)
bss段(bss segment)。 bss是英文Block Started by Symbol的简称。
文件映射区,是进程打开文件后,将文件内容从硬盘读到进程的区域,以后就直接在内存中操作这个文件,读写完成后保存时,再将内存中的文件写到硬盘去。
内核映射区:将OS内核程序映射到这个区域。
#include <stdio.h>
#include <stdlib.h>
int var1=1; //data段
int var2; //bss段
int var3=0; //bss段
const int var4=1; //rodata段
int main(void){
int var5=1; //栈
static int var6=1; //data段
static int var7; //bss段
int *p=(int *)malloc(sizeof(int));
int var8=*(p+0); //堆
free(p);
return 0;
}
注意:OS下和裸机下C程序加载执行的差异。
裸机下,若定义一个全局变量初始化为0但实际上不为0,应在static.S中加清bss段代码。
在OS下,自动完成重定位和清bss段,所以我们可以看到:C语言中未初始化的全局变量默认为0。
注意:初始值
#include <stdio.h>
#include <stdlib.h>
int global;
static int static_Global;
int main(void){
int local;
static int static_local;
int *p=(int *)malloc(1000*sizeof(int));
printf("%d \n",global); //0
printf("%d \n",static_Global); //0
printf("%d \n",local); //-858993460(编译器会提醒错误)
printf("%d \n",static_local); //0
printf("%d \n",*(p+0)); //-842150451
free(p);
return 0;
}
作用域
作用域是描述变量起作用的代码范围。
#include <stdio.h>
int var=1; //作用域为本文件
int main(void){
printf("in file,var = %d \n",var);
int var=2; //作用域为main函数
if(1){
int var=3; //作用域为if
printf("in if,var = %d \n",var);
}
printf("in main,var = %d \n",var);
return 0;
}
生命周期
生命周期是描述变量什么时候诞生,什么时候死亡。程序运行时分配内存空间给这个变量,使用后回收这个内存空间,此后内存地址和这个变量无关了。
链接属性
在C语言中,当组成一个程序的各个源文件分别被编译之后,所有目标文件以及那些从一个或多个函数库中引用的函数接在一起,形成可执行程序。而在不同文件中可能会定义相同的标识符(变量)因此链接属性就是来决定如何处理这种类型的标识符的。
链接属性一共有三种:external(外部)internal(内部)和 none none也被称为没有链接属性的标识符 。 因此none属性的总会被认为是独立的实体 即无论有多少个相同的标识符 都会认为是不同的。 external指的是在所有的源文件中表示这一个实体,而internal指的是在同一个源文件中表示同一个个体。
用auto、static、extern、volatile等关键字修改链接属性。
通俗讲:程序运行时,变量名、函数名能够和相应的内存对应起来,靠符号来链接。
static
static修饰局部变量,形成静态局部变量
static修饰全局变量,形成静态全局变量
extern
a.c使用var时应先声明,原型和声明格式一样,将来在链接的时候链接器会在别的.o文件找到这个同名变量。
同理
a.c使用fun()时应先声明,原型和声明格式一样,将来在链接的时候链接器会在别的.o文件找到这个同名函数。
//a.c文件
#include <stdio.h>
extern int var;
extern fun();
int main(){
printf("%d \n",var);
fun();
return 0;
}
//b.c文件
int var=1;
int fun(){
return 0;
}
volatile
如果没有初始化ili9341,向ili9341写(0x0C)命令,会返回DBI[2:0]为111的数据
![](https://i-blog.csdnimg.cn/blog_migrate/f7eddbd07beebbef44117a4088936428.png)
现在已经初始化ili9341为16位像素的格式,向ili9341写(0x0C,即读取像素格式)命令,会返回DBI[2:0]为101的数据。结果返回0x00,这是为什么呢?
#define ILI9341_CMD_ADDR (uint16_t*)(0x60000000)
#define ILI9341_DATA_ADDR (uint16_t*)(0x60020000)
__inline void ILI9341_Write_Cmd ( uint16_t usCmd ){
*ILI9341_CMD_ADDR = usCmd;
}
__inline uint16_t ILI9341_Read_Data ( void ){
return ( *ILI9341_DATA_ADDR );
}
uint16_t Read_Pixel_Format(void)
{
ILI9341_Write_Cmd(0x0C);
ILI9341_Read_Data();
return ILI9341_Read_Data();
}
原来是定义ili9341的相关地址时,没有加volatile,编译器会有优化操作,会把ILI9341_DATA_ADDR的值从内存加载到内核里的寄存器,如果代码里没有语句对变量进行修改,编译器以为这个值没有变化,会告诉CPU说:你以后找ILI9341_DATA_ADDR里的值,就不用去内存里取了,直接用寄存器里缓冲的值吧,因为它没有被修改过。
#define ILI9341_CMD_ADDR (volatile uint16_t*)(0x60000000)
#define ILI9341_DATA_ADDR (volatile uint16_t*)(0x60020000)
但是ili9341确确实实把ILI9341_DATA_ADDR里的值修改了(代码里没有对这个值进行修改),结果读到了0x00;
这些不起眼的问题,有时就是编程路上的拦路虎,要多加小心。
编译器一般会优化从内存复制到寄存器的操作,节省CPU取内存的操作
加了volatile在每次取变量值时,编译器可自由地选择变量存放的位置,不需要时会被清除,中断ISR引用的变量、多线程共用的变量,硬件会更改的变量(上面就是这个例子),要加volatile。
https://blog.csdn.net/didi1663478999/article/details/98523122
volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人
总结
各种变量(static、局部、全局、动态内存等等)的生命周期、链接属性、作用域、内存区域、默认值
![](https://i-blog.csdnimg.cn/blog_migrate/b1a3be47ba842edb2de3ac854a4a3de8.png)
第八章 C语言关键细节讨论
操作系统
实际上操作系统(OS)也是一个软件,只是这个软件用于实现计算机硬件管理,达到高效利用计算机资源、高效开发大型项目和高效升级软件的目的。OS 处在了上层应用和下层硬件之间的中间层。OS 对下管理硬件,对上向应用提供服务接口。分别是 CPU 管理、内存管理、任务管理、文件管理和 I/O 设备管理。
![](https://i-blog.csdnimg.cn/blog_migrate/fb7c91d3bf6ef45abb26292e29f439f5.png)
C库函数
库函数就是由权威人士实现好的各种工具类函数,将这些工具类函数所在文件编译成各种.o文件,然后再将这些.o文件集合到一起,这个集合就被称为库。当我们需要使用到这些工具函数时,只需要链接相应库,程序会自动地去库中找到我们需要的具体函数,并加载运行。
库又分为静态库和动态库(共享库 )。
静态库是直接将库函数复制到内存,如果有很多引用都使用到这个静态库的话,这个静态库会在内存中有很多副本。所以静态库耗费内存,但是节省时间。
而对于动态库,如果有很多程序使用同一个库函数,但是动态库在内存中只会被加载一份,所有的程序共享同一个动态库,因此动态库又被称为共享库。动态库节省了内存,但是耗费了时间。
main函数传参及返回值
#include <stdio.h>
/*
argc(argument count)参数个数
argv(argument value)参数值
*/
int main(int argc, char *argv[]){
printf("%d\n",__LINE__);
printf("%s\n",__FILE__);
printf("%s\n",__DATE__);
printf("%s\n",__TIME__);
return 0;
}
![](https://i-blog.csdnimg.cn/blog_migrate/15e622d7ca5d1b4d56e1717633c33265.png)
![](https://i-blog.csdnimg.cn/blog_migrate/14a9e6521dee598ae2d5f0f7f8a3b249.png)
void类型
void类型是不知道类型,不确定类型。不是空类型
void a;
a=5; //出错,没确定具体类型,无法给它分配内存空间
malloc函数原型为void *malloc(unsigned int size)
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *p=(int *)malloc(1000*sizeof(int)); //由void * 强制转换为int *
free(p);
return 0;
}
如果是int *p=malloc(1000*sizeof(int)); 程序将会无法通过编译,因为malloc无法返回具体类型的指针。
注意:当使用void类型时一般都是void * ,void * 可以指向任何类型的数据
debug宏
debug和release版本其实是一套源代码,不同的是debug宏有很多打印调试的信息。靠条件编译来控制生成debug和release版本。
#ifdef DEBUG
#define DBG() printf()
#else
#define DBG()
#endif