七、存储类&作用域&生命周期&链接属性
7.1、概念词:存储类(栈、堆、数据区、.bss段、.text段)
作用域(代码块作用范围,也就是变量作用的范围)
生命周期(变量的诞生和死亡)
链接属性(外链接属性、内链接属性、无连接属性)
7.2、Linux下的内存映射(分配情况、组织情况):见图内存映射。其中有关进程的空间,如进程控制块、页表等都是在内核里面的。文件区是映射外部文件的,如打开记事本,那么这个文件临时存放在文件区域。(见引用资料)
问题:虚拟地址技术? 解决:后期在Linux应用/网络编程会讲。
OS下和裸机下C程序加载执行的差异? 解决:在arm裸机第十六部分有介绍。
7.3、存储类关键字:
<1> auto 自动的(一个用法:修饰局部变量,在定义变量时可以省略) 【外链接:与第二个c文件链接】【内链接:只与本c文件链接】【无连接:就是无链接】
<2> static 静态的(有两个用法,第一个是修饰局部变量,意思是当作全局变量,存放在数据区,作用域只是定义的那个函数范围,生命周期和整个程序一样,属于无连接
第二个是修改全局变量/函数,意思是这个全局变量/函数只在当前c文件有效,其他c文件是不能使用它的,属于内链接,普通全局变量属于外连接)
<3>register 寄存器(一个用法,修饰变量,作用是让编译器把这个变量放在寄存器中,当这个变量频繁的被使用时,用这个方法可以提高效率,但有时候不一定就放在寄存器,因为寄存器是有限的,没有剩余的寄存器了)
<4>extern (一个用法,修饰全局变量,表示该文件要使用的这个变量,在另外一个c文件中已经定义了,其一个声明的作用,不能初始化)
<5>volatile (一个用法,修饰变量,表示对这个变量的语句不要去优化)
(1) volatile的字面意思:可变的、易变的。C语言中volatile用来修饰一个变量,表示这个变量可以被编译器之外的东西改变。编译器之内的意思是变量的值的改变是代码的作用,编译器之外的改变就是这个改变不是代码造成的,或者不是当前代码造成的,编译器在编译当前代码时无法预知。譬如在中断处理程序isr中更改了这个变量的值,譬如多线程中在别的线程更改了这个变量的值,譬如硬件自动更改了这个变量的值(一般这个变量是存在寄存器,或许当其他进程要用到这个寄存器时,就把这个寄存器的变量给改变了,同时也就改变了这个变量)
(2) 以上说的三种情况(中断isr中引用的变量,多线程中共用的变量,硬件会更改的变量)都是编译器在编译时无法预知的更改,此时应用使用volatile告诉编译器这个变量属于这种(可变的、易变的)情况。编译器在遇到volatile修饰的变量时就不会对改变量的访问进行优化,就不会出现错误。
(3) 编译器的优化在一般情况下非常好,可以帮助提升程序效率。但是在特殊情况(volatile)下,变量会被编译器想象之外的力量所改变,此时如果编译器没有意识到而去优化则就会造成优化错误,优化错误就会带来执行时错误。
而且这种错误很难被发现。
(4) volatile是程序员意识到需要volatile然后在定义变量时加上volatile,如果你遇到了应该加volatile的情况而没有加程序可能会被错误的优化。如果在不应该加volatile而加了的情况程序不会出错只是会降低效率。
所以我们对于volatile的态度应该是:正确区分,该加的时候加不该加的时候不加,如果不能确定该不该加为了保险起见就加上。
举例子1: int a=3 ,b,c;
b=a;
c=b;
那么编译器会优化成 c=b=a=3; 如果此时遇到上述三种情况,突然改变了a的值,那么,对于没有优化前是对的,但是对于优化后,那么c仍然是3,就会出错。
所以当我们程序员知道这个变量会发生改变时,就应该加 volatile,提示编译器不要帮我们做优化。
举列子2:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
这段代码的有个恶作剧。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
<6> restrict (1)c99中才支持的,所以很多延续c89的编译器是不支持restrict关键字,gcc支持的。
(2)restrict 作用是让编译器对这个变量做一些优化,让它提高效率。下面的网站有列子。
(3)restrict只用来修饰指针,不能修饰普通变量,它表示只能该指针才能修改它的内容。如用memcpy时,两个内存存在重叠的现象。
(4)https://blog.chinaunix.net/uid-22197900-id-359209.html (这个网站里面有详细的例子)
(5)memcpy和memmove的区别 void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n);这样它可以优化成memmove原理的方式(当存在重叠时,先复制到一段内存空间,然后再把它复制到目的空间)
7.4、作用域:
(1)全局变量起名字一般是 g_a;
(2)名字加前缀表示
7.5、总结:<1>局部变量地址由运行时在栈上分配得到,多次执行时地址不一定相同,函数不能返回该类变量的地址(指针)作为返回值。
<2>为什么要分为数据段和.bbs段?因为当加载到内存重定位时,如果这些数据(包括0)一个一个的复制,会降低效率,为0的变量,直接清内存就可以了,这样提高效率。
<3>在头文件声明全局变量时, extern int a; 声明函数就是 void int max(int a, int b);
<4>写程序尽量避免使用全局变量,尤其是非static类型的全局变量。能确定不会被其他文件引用的全局变量一定要static修饰。(因为全局变量占内存的时间是最长的,要看你的变量是不是需要这么长的时间,这样可以节约内存空)
往期文章列表:****往期热文:
基础C语言知识串串香(1)
===========我是华丽的分割线===========
更多知识:
点击关注专题:嵌入式Linux&ARM
或浏览器打开:https://www.jianshu.com/c/42d33cadb1c1
或扫描二维码: