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

概念解析

存储类
(1)存储类就是存储类型,也就是描述C语言变量在何种地方存储
(2)内存有很多管理方法:堆,栈,数据段,bss段,text段,一个变量的存储类型属性就是描述这个变量存储在何种内存段上
(3)局部变量分配在栈上,所以它的存储类型就是栈;显示初始化为非0的全局变量分配在数据段,显示初始化为0和没有显示初始化(默认为0)的全局变量分配在bss段
作用域
(1)作用域是描述变量起作用的代码范围
(2)基本来说:变量的作用域就是代码块作用域。意思是这个变量起作用的范围是当前的代码块。代码块就是一对大括号{}括起来的范围,所以一个变量的作用域是:变量定义所在的{}范围内从这个变量定义开始往后的部分
生命周期
(1)生命周期是描述这个变量声明时候诞生以及什么时候死亡
链接属性
(1)源代码到可执行程序:编译,链接
(2)编译就是把源程序搞成.o目标文件,目标文件里面很多符号和代码段,数据段,bss段。符号就是编程中的变量名,函数名。运行时变量名,函数名能够和相应的内存对应起来,靠符号来做链接的
(3).o的目标文件链接生成最终的可执行程序的时候,其实就是把符号和相应的段给链接起来。C语言的符号有三种链接属性:外连接属性,内链接属性,无连接属性

linux下C程序的内存映像

linux C的内存模型
请添加图片描述
代码段,只读数据段
(1)对应程序中的代码(函数),代码段在linux中又叫文本段(.text)
(2)只读数据段就是在程序运行期间只能读不能写的数据,const修饰的常量有可能是存在只读数据段的
数据段,bss段
(1)数据段存:显示初始化为非0的全局变量;显示初始化为非0的static局部变量
(2)bss段存:显示初始化为0或者未显示初始化的全局变量;显示初始化为0或未显示初始化的static局部变量

(1)C语言不会自动向堆中存放东西,是程序手动存放的
文件映射区
(1)文件映射区就是进程打开文件后,将这个文件的内容从硬盘读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完后保存时将内存中的文件写到硬盘中

(1)栈内存区:局部变量分配在栈上:函数调用传参的过程也会用到栈
内核映射区
(1)内核映射区就是将操作系统内核程序映射到这个区域了
(2)对于linux中的每一个进程来说,它都移位整个系统中只有它自己和内核而已。它认为内存地址0xc0000000以下都是它自己的活动空间,0xc0000000以上是OS内核的活动空间
(3)每一个进程都是活在自己独立的进程空间中,0-3G的空间每一个进程是不同的)使用了虚拟地址技术,但是内核是唯一的
OS下和裸机C程序加载执行的差异
(1)C语言程序运行时环境有一定要求,意思是单独个人写的C语言程序没法直接在内存中运行,需要外部一定的协助,这段协助的代码叫加载运行代码(或者叫构建C运行时环境的代码,这一段代码在操作系统下是别人写好的,会自动添加到我们写的程序上,这段代码的主要作用是:给全局变量赋值,清bss段)
(2)在操作系统中执行程序的时候,会自动重定位和清bss段,所有C语言中为初始化的全局变量默认是0
(3)数据段的全局变量是静态局部变量都是由非0的初值的,这些初值在main函数运行之前就已经被初始化了,是重定位器件完成的初始化

存储类相关的关键字

auto
(1)auto关键字在C语言中只有一个作用,那就是修饰局部变量
(2)auto修饰局部变量,表示这个局部变量是自动局部变量,自动局部变量分配在栈上(在栈上,说明不初始化就是随机的)
(3)平时定义局部变量时就是定义auto的,只是省略了auto关键字而已,可见,auto的局部变量其实就是默认定义的普通的局部变量
static
(1)在C语言中有两种用法:没有彼此关联,完全独立的。
(2)static的第一种用法是:用来修饰局部变量,形成静态局部变量。本质区别是存储不同(存储类不同就衍生很多不同);非静态局部变量分配在栈上,而静态局部变量分配在数据段/bss段上。
1.静态局部变量在存储类方面和全局变量一样
2.静态局部变量在生命周期方面和全局变量一样
3.静态局部变量和全局变量的区别是:作用域,链接属性。静态局部变量作用域是代码块作用域(普通局部变量是一样的),链接属性数无连接。全局变量作用域是文件作用域(和函数一样),链接属性方面是外连接
(3)static的第二种用法是:用来修饰全局变量,形成静态全局变量。区别是在链接属性上不同
rigister
(1)rigister关键字不常用,也只用一个作用,那就是:register修饰的变量(一般是全局变量)编译器会尽量将它分配在寄存器中。(平时分配的一般的变量都是在内存中的)分配在寄存器中一样的用。但是读写效率会高很多。所以register修饰的变量用在那种变量被反复高频率使用,通过改善这个变量的访问效率可以极大的提升程序运行效率。所以rigister是极致提升效率的一种方式
(2)uboot中用到一个register类型的变量,gd这个变量是用来存uboot的全局变量(gd就是global data)因为这个全局变量在整个uboot中到处都被访问,所以定义成register的
(3)平时写代码要被定义成register这种情况很少,一般慎用
(4)register编译器只能承诺尽量将register修饰的变量放在寄存器中,但是不保证一定放在寄存器,因为寄存器数量有限,不一定有用。
extern
(1)主要用来声明全局变量,声明的目的只要是在a.c中定义全局变量而在b.c中使用该变量
(2)C语言中程序的编译时以单个.c源文件为单位的,因此编译a.c时只考虑a.c的内容(不会考虑b.c的内容),这就导致a.c中使用了b.c中的定义的变量时在编译时报错。解决方案就是声明
(3)应该在a.c中使用g_b之前先声明g_b,声明就是告诉a.c我在别的文件中定义了g_b,并且它的原型和声明一样,将来在链接的时候链接器会在别的.o文件中找到这个同名变量。声明一个全局变量就要用到extern关键字
volatile
(1)volatile的字面意思是:可变的,易变的。C语言中volatile用来修饰一个变量,表示这个变量可以被编译器之外的东西改变。编译器之内的意思是变量的值得改变是代码的作用,编译器之外的改变就是这个高边不是代码造成的,或者不是当前代码造成的,编译器在编译当前代码时无法预知。譬如在中断处理程序isr中更改了这个变量的值。譬如多线程在别的线程改变了这变量的值,譬如硬件更改了这个值(一般这个变量是一个寄存器的值)
(2)中断isr中引用的变量,多线程中公用的变量,硬件会更改的变量,都是编译器在编译时无法预知的更改,此时应用使用volatile告诉编译器这个变量属于这种(可变的,易变的)。编译器在遇到volatile修饰的变量是就不会对该变量的访问进行优化,就不会错误
(3)编译器的优化在一般情况下非常好,可以帮助提升效率。但是在特殊情况(volatile)下,变量会被编译器想象之外的力量所改变,此时如果编译器没有意识到而去优化就会造成优化错误,优化错误就会带来执行时的错误,而且这种错误很难被发现
(4)volatile是程序员意识到需要volatile然后在定义变量是加上volatile,在不需要加的地方加上,只是会降低程序的效率,.
但是在不能区分是否加volatile的地方,最好加上,以防程序错误
restrict
(1)只能修饰指针,不能修饰普通变量
(2)意思是只有这个指针所在的位置可以修改该指针,不能在别的地方修改,restri了之后,编译器会进行优化,不加就不会优化
typedef
(1)typedef是存储类关键字,但是实际上和存储没有关系

作用域详解

局部变量的代码块作用域
(1)代码块基本可以理解为一对大括号{}括起来的部分
(2)代码块不等于函数,因为if while for都有{}。所以代码块<=函数
(3)局部变量的作用域是代码块作用域,也就是说一个局部变量可以被访问和使用的范围仅限于定义这个局部变量的代码块中定义式之后的部分
函数名和全局变量的文件作用域
(1)文件作用域的意思是全局的访问权限,也就是说整个.c文件中都可以访问这些东西。这就是所说的全局和局部,全局就是文件作用域
(2)详细的说:函数和全局变量的作用域是定义所在的整个.c文件之内定义式之后的部分
(3)不管是局部变量,全局变量,函数,都要定义才能使用
(4)严格来说我们上面的总结是错误。准确的来说:全局变量/函数的作用域都是自己所在的文件,但是定义式之前的部分因为缺少声明所以没法使用,解决方案;1把它定义到前面去;2.定义到后面但是是在前面加声明;局部变量因为没法声明
(5)所有的局部变量必须先定义在最前面,在变量定义前不能有一句执行代码
同名变量的掩蔽规则
(1)首先,如果两个同名变量作用域不同没有交集,这种情况下同名没有任何影响
(2)其次,如果两个同名变量作用域有交叠,C语言规定,作用域小的一个变量会掩蔽作用域大的那个

变量的生命周期

研究变量的生命周期
(1)研究变量的生命周期,有助于理解变量的行为特征
栈变量的生命周期
(1)局部变量(栈变量)存储在栈上,生命周期是临时的。临时的意思就是说:代码执行过程中按照需要去创建,使用,消亡
(2)函数定义的局部变量,在这个函数每一次被调用时都会创建一次,然后使用,最后在函数返回的时候消亡
堆变量的生命周期
(1)堆内存空间是客观存在的,是由操作系统维护的。我们程序只是去申请后使用然后释放
(2)堆变量的生命周期,就是从malloc申请时诞生,然后使用,直到free之后不能去访问
(3)所以堆内存在malloc之前和free之后不能再去访问,因此堆内存在实践编程时都是被反复的malloc和free的
数据段,bss段变量的生命周期
(1)全局变量的生命周期是永久的
(2)全局变量所占用的内存是不能被程序自己释放的,所以程序如果申请了过多的全局变量会导致这个程序一直占用大量内存。
代码段,只读段的生命周期
(1)永久的
(2)有时候放在代码段的不只是代码,还有const类型的常量,还有字符串常量(const类型的常量,字符串常量有时候放在rodata段,有时候放在代码段,取决于平台)

链接属性

编译以文件为单位,链接以过程为单位
(1)编译器工作时是将所有的源文件依次读进来,单个为单位进行编译的
(2)链接的时候实际上是把第一步编译生成的单个.o文件整体的输入,然后处理链接成一个可执行程序
三种链接属性:外连接,内链接,无链接
(1)外连接:意思是在整个范围内可以找到它,(言下之意就是跨文件)函数和全局变量属于外连接
(2)内连接的意思就是(C文件内部)内部链接属性,也就是说可以在当前C文件内部范围内进行连接(不能在当前C文件外面的其它C文件中进行访问,链接)static修饰的函数/全局变量属于内连接
(3)无链接的意思就是这个符号本身不参与链接,和链接无关。所有的局部变量(auto的static)都是无链接的
函数和全局变量的同名冲突
(1)因为函数和全局变量是外部链接属性,就是说每一个函数和全局变量将来在整个程序中所有的C文件都能被访问,因此在一个程序中的所有的C文件中不能出现同名的函数/同名的全局变量
(2)最简单的解决方案就是起名不要重复,但是很难做到。一个很大的工程中函数和全局变量太多了,很难保证不重名。
(3)现代高级语言中完美解决的方案就是命名空间(namespace)
(4)C语言为了解决重名的问题,采用了三种链接属性的方案
(5)我们将明显不会在其他C文件中引用(只在当前C文件中引用)的函数/全局变量,使用static修饰使其称为内连接属性,这样将来在链接是即使2个C文件中有重名的函数/全局变量,只要一个或2个为内连接属性就没事
(6)这样在一定程度上解决这个问题,但是没有从根本上解决,
static的第二种用法:修饰全局变量和函数
(1)普通的(非静态)的函数/全局变量,默认是链接属性是外部的
(2)static(静态)的函数/全局变量,链接属性是内部链接。

最后的总结

(1)普通(自动)局部变量分配在栈上,作用域为代码块作用域,生命周期是临时,链接属性为无链接,自定义如果未显示初始化则值为随机,变量地址由运行时在栈上分配得到,多次执行时地址不一定相同,函数不能返回该类变量的地址(指针)作为返回值
(2)静态局部变量分配在数据段/bss段(显示初始化为非0则在数据段,显示初始化为0或未显示初始化则在bss段),作用域为代码块作用域,生命周期为永久,链接属性为无链接,定义时如果未显示初始化则值为0,变量地址由运行时环境在加载程序时确定,整个程序运行过程中唯一不变;静态局部变量其实就是作用域为代码块作用域(同时链接属性为无链接)的全局变量。静态局部变量可以改为全局变量实现(程序中尽量避免用全局变量,会破坏结构性)
(3)静态全局变量/静态函数和普通全局变量/普通函数的唯一区别就是:static使全局变量/函数的链接属性由外部链接(整个程序范围)转为内部链接(当前C文件)。这是为了解决全局变量/函数重名问题,(C语言没有命名空间namespace的概念)将不必要被其他文件引用的全局变量/函数声明为static可以很大程序上改善重名问题
(4)写程序尽量不要写全局变量,尤其是非static类型的全局变量,能确定不会被其他文件引用的全局变量一定要static修饰
(5)注意区分全局变量的定义和声明。一般规律下:如果定义的同时有初始化则一定会被认为是定义:如果只是定义而没有初始化则有可能被编译器认为是定义,也可能被认为是声明,要具有体分析:如果使用extern则可能会被认为是声明
(6)全局变量应该定义在C文件中并且在头文件中声明,而不要定义在头文件中(因为如果定义在头文件中,则该头文件被多个C文件包含该全局变量会重复定义)
(7)在b.c中引用a.c中定义的全局变量/函数有2种方法:一是在a.h中声明该函数/全局变量,然后在b.c中#include:二是b.c中使用extern显示声明要引用的函数/全局变量。其中一种方法比较正式
(8)存储类决定生命周期,作用域决定链接属性
(9)宏和inline函数的链接属性为无链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值