《C语言内核深度解析》——笔记及拓展(4)

文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。

以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。

本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

第七章 存储类、作用域、生命周期、链接属性

  • 存储类

存储类,就是存储类型。

内存被分为了栈、堆、数据段、bss段和text段等。

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

之前在学STM32时,遇到了这样一个问题:

如果没有初始化ili9341,向ili9341写(0x0C)命令,会返回DBI[2:0]为111的数据

现在已经初始化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、局部、全局、动态内存等等)的生命周期、链接属性、作用域、内存区域、默认值

第八章 C语言关键细节讨论

  • 操作系统

实际上操作系统(OS)也是一个软件,只是这个软件用于实现计算机硬件管理,达到高效利用计算机资源、高效开发大型项目和高效升级软件的目的。OS 处在了上层应用和下层硬件之间的中间层。OS 对下管理硬件,对上向应用提供服务接口。分别是 CPU 管理、内存管理、任务管理、文件管理和 I/O 设备管理。

  • 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;
}
  • 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值