C语言高级-存储类、作用域、生命周期、链接属性

第一点

概念解析

存储类

一个变量的存储类属性就是描述这个变量存储在何种内存段中;

作用域

对以下函数进行解读

如图可知虽然在for循环内部定义了a,但是在函数外部定义的a++还是显示错误,显示没有定义;这就是作用域的问题!

生命周期

生命周期就是描述这个变量什么时候诞生(运行时分配内存空间给这个变量)以及什么时候死亡(就是收回给这个变量分配的内存空间)的时间间隔!

链接属性

编译是将源代码的.C文件编译成.O文件(单个的二进制机器码格式),.O文件中就有很多的符号和代码段、数据段、bss段等分段,符号就是我们知道的变量名和函数名,链接就是将我们的函数名、符号名与他所对应的内存分段所对应起来 

链接也是将每个.c文件生成的.o文件,连接成一个整体的二进制机器码的可执行文件!

链接的属性分为3种:内链接、外链接、无链接

(1)外链接:外部链接属性,也就是说这家伙可以在整个程序范围内(言下之意就是可以跨文件)进行链接,譬如普通的函数和全局变量属于外连接

(2)内链接:(c文件内部)内部链接属性,也就是说这家伙可以在当前c文件内部范围内进行链接(言下之意就是不能在当前c文件外面的其他c文件中进行访问、链接)。static修饰的函数/全局变量属于内链接。

(3)无链接:无连接的意思就是这个符号本身不参与链接,它跟链接没关系。所有的局部变量(auto的、static的)都是无连接的

第二点

linux c的内存映象

文件映射区

文件映射区就是进程打开了文件后,将这个文件的内容从硬盘中读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完了以后在保存时再将内存中的文件写到硬盘中去。

内核映射区

内核映射区就是将操作系统内核程序映射到这个区域了

对于linux中每一个进程来说,他都以为整个系统中只有他和内核而已,他认为内存地址0XC0000000以下都是它自己的活动空间,以上就是OS内核的活动空间,每一个内存都是独自行动互不打扰(具体图示如下)然后每一个进程与内核通信都有固定的API接口!

第三点

关键字

auto

作用:在C语言中只有一个作用,修饰局部变量。表示这个局部变量是自动局部变量,自动局部变量分配在栈上,既然是分配在栈上,如果不初始化,那么值就是随机的,生命周期就是临时的,平时定义的局部变量其实就是atuo的,只是省略了这个关键字!

static

作用:

(1)用来修饰局部变量,形成静态局部变量!本质区别就是内存存储区不同,非静态的局部变量在栈 上,但是静态的局部变量在数据段/bss段。

(2)用来修饰全局变量,形成静态全局变量!他与其他类型的全部变量的区别主要在于链接属性的不同!(上面有说明)

register

作用:用register修饰的变量(一般是全局变量)编译器会尽量(也就是不一定,如果有空闲才会放在寄存器,如果没空闲,还是在内存中)把他分配在寄存器中,平时分配变量一般都是分配在内存中。既然是分配在寄存器中,那么cpu读取指令的速度又快了一点,极大的提高了程序运行的效率。

extern

作用:声明全局变量;比如在a.c中定义了全局变量fengpan=1029,但是在b.c中要去使用这个变量,就应该在b.c中去声明这个变量,就是相当于告诉b.c这个变量我再其他的文件定义过,现在可以直接拿过来用!(示例在b.c中使用 extern fengpan   注意:不要写成fengpan=1029

volatile

作用:字面意思就是可变的、易变得。他可以在运行的时候被编译器优化,这种优化很多种情况下是有好处的,比如下面的程序分析:

如果定义整形变量a\b\c,然后给a赋值3,然后执行后面两个指令,一般的执行顺序是,将a 中的值读出来写进b,然后将b中值读出来写进c,这样的话就需要读三次,写三次,很费时间

但是编译器优化就是将a中的值读进一个寄存器,然后从此寄存器中写进a、b、c这样的话就省了时间,很方便!

但是:如果就在a=3之后,突然进来一个中断【(中断isr中引用的变量,多线程中共用的变量,硬件会更改的变量)都是编译器在编译时无法预知的更改】,中断的内容是重新给a赋值为5,那如果这种情况下还是被编译器优化,那中断之后的b、c不还是3嘛!  这样的话,编译器的优化并没有起到一个正向的作用!

因此,为了防止以上事件的发生,我们就需要在我们所定义的变量前面增加volatile,使得编译器的额优化失效!

编译器的优化一定程度上是会增加程序执行的效率的,因此每个定义之前都加volatile是不现实的,这就需要我们本身去提前判断是不是会有上面蓝色字体的情况发生,如果有,我们就加volatile,如果没有,就不需要加!如果不能确定加不加,那就加!!!

restrict

作用:概括的说,关键字restrict只用于限定指针;该关键字用于告知编译器,所有修改该指针所指向内容的操作全部都是基于该指针的,即不存在其它进行修改操作的途径;这样的后果是帮助编译器进行更好的代码优化,生成更有效率的汇编代码。

例子如下:

restrict关键字用法_The_Hungry_Brain的博客-CSDN博客_restrict关键字

第四点

函数和全局变量重名的问题

对于这种问题,最直接的方法本来就是,命名的时候要注意重命名的问题,但是C语言的工程有时候过于庞大,因此重命名问题在所难免;

之后也有了一种新的解决方案,就是在变量和函数面前加上前缀修饰(命名空间namespace)但是c语言不是这么解决的;

c语言解决的方案就是前面我们所说过的三种链接属性的方法;我们将明显不会在其他c文件中引用(只在当前c文件中引用)的函数/全局变量,使用static修饰使其成为内链接属性,这样在将来连接时即使2个c文件中有重名的函数/全局变量,只要其中一个或2个为内链接属性就没事。

虽然这种方法一定程度上,降低了重名问题,但根本上是解决不了的,工程一大,这种问题还是会出现,所以C语言很难写大一些的工程。

第五点

总结

(1) 普通(自动)局部变量分配在栈上,作用域为代码块作用域,生命周期是临时,连接属性为无连接。定义时如果未显式初始化则其值随机,变量地址由运行时在栈上分配得到,多次执行时地址不一定相同,函数不能返回该类变量的地址(指针)作为返回值。


(2) 静态局部变量分配在数据段/bss段(显式初始化为非0则在数据段,显式初始化为0或未显示初始化则在bss段),作用域为代码块作用域(人为规定的),生命周期为永久(天然的),链接属性为无连接(天然的)。定义时如果未显式初始化则其值为0(天然的),变量地址由运行时环境在加载程序时确定,整个程序运行过程中唯一不变;静态局部变量其实就是作用域为代码块作用域(同时链接属性为无连接)的全局变量。静态局部变量可以改为用全局变量实现(程序中尽量避免用全局变量,因为会破坏结构性)。


(3) 静态全局变量/静态函数和普通全局变量/普通函数的唯一差别是:static使全局变量/函数的链接属性由外部链接(整个程序所有文件范围)转为内部链接(当前c文件内)。这是为了解决全局变量/函数的重名问题(C语言没有命名空间namespace的概念,因此在程序中文件变多之后全局变量/函数的重名问题非常严重,将不必要被其他文件引用的全局变量/函数声明为static可以很大程度上改善重名问题,但是仍未彻底解决)。


(4) 写程序尽量避免使用全局变量,尤其是非static类型的全局变量。能确定不会被其他文件引用的全局变量一定要static修饰,(只有这样才会内链接,不然全局变量就会被外链接了,就会碰到重名问题)


(5) 注意区分全局变量的定义和声明。一般规律如下:如果定义的同时有初始化则一定会被认为是定义;如果只是定义而没有初始化则有可能被编译器认为是定义,也可能被认为是声明,要具体分析;如果使用extern则肯定会被认为是声明(实际上使用extern也可以有定义,实际上加extern就是明确声明这个变量为外部链接属性)。


(6) 全局变量应该定义在c文件中并且在头文件中声明,而不要定义在头文件中(因为如果定义在头文件中,则该头文件被多个c文件包含时该全局变量会重复定义)。


(7) 在b.c中引用a.c中定义的全局变量/函数有2种方法:一是在a.h中声明该函数/全局变量,然后在b.c中#include <a.h>;二是在b.c中使用extern显式声明要引用的函数/全局变量。其中第一种方法比较正式。


(8) 存储类决定生命周期,作用域决定链接属性


(9) 宏和inline(内联)函数的链接属性为无链接。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值