37.作用域和生命周期和链接属性

来源:互联网 发布:mac怎么更改字体大小 编辑:程序博客网 时间:2024/06/10 00:14

37.1.作用域详解
(1)局部变量的代码块作用域;代码块即1对大括号{}括起来的部分;代码块不等于函数,因为if/while/for都有{},则代码块<=函数;局部变量的作用域是代码块作用域,即某个局部变量可以被访问和使用的范围仅限于定义该局部变量的代码块中定义式之后的部分。
(2)函数名和全局变量的文件作用域;文件作用域的意思就是全局的访问权限,即整个.c文件中都可以被访问,这就是平时所说的局部和全局,全局就是文件作用域;函数和全局变量的作用域是定义所在的整个.c文件之内定义式之后的部分。
(3)全局变量/函数的作用域是自己所在的文件,但定义式之前的部分因为缺少声明所以没法使用,解决方案1是把它定义到前面去,解决方案2是定义到后面但是在前面加声明;局部变量因为没法声明,则只能定义在前面去。
(4)在c89标准的编译器中(现在很多编译器还延续使用c89标准),所有的局部变量必须先定义在最前面,在变量定义之前不能有1句执行代码;在c99标准的编译器中(gcc兼容c99标准)可以允许在代码块内任意地方定义变量,但还是只能在定义了之后的区域使用,在定义之前的区域还是不能使用。
(5)同名变量的掩蔽规则;编程时不可避免会出现同名变量,变量同名后不一定会出错;若两个同名变量作用域不同且没有交叠则同名没有任何影响;若两个同名变量作用域有交叠,C语言规定在作用域交叠范围内,作用域小的那个变量会掩蔽掉作用域大的那个变量。


37.2.生命周期详解
(1)栈变量的生命周期;局部变量(栈变量)存储在栈上,其生命周期是临时的,即代码在执行过程中可按照需要去创建/使用/消亡;譬如某函数内定义的局部变量,在该函数每次被调用时都会创建1次,然后使用,最后在函数返回的时候消亡;则某个函数内的局部变量在函数外不能使用。
(2)堆变量的生命周期;堆内存空间是客观存在的,是由操作系统维护的,我们程序只是去申请然后使用然后释放;我们只关心程序使用堆内存的该段时间,则堆变量也有了自己的生命周期即从malloc申请时诞生,然后使用,直到free时消亡;则堆内存在malloc之前和free之后不能再去访问,因此堆内存在实践编程时都是被反复的malloc和free的。
(3)数据段/bss段变量的生命周期;全局变量的生命周期是永久的,即程序被执行时诞生,在程序终止时消亡;全局变量所占用的内存是不能被程序自己释放的,则程序若申请了过多的全局变量会导致该程序一直占用大量内存。
(4)代码段/只读段的生命周期;即程序执行的代码/函数,它的生命周期是永久的,但代码的生命周期我们并不关注;有时候放在代码段的不只是代码,还有const类型的常量和字符串常量(const类型的常量/字符串常量有时放在rodata段,有时候放在代码段,取决于平台)。


37.3.链接属性详解
(1)C语言程序的组织架构=多个C文件+多个h文件;完整的某个C语言程序(譬如linux内核、uboot)是由多个c文件和多个h文件组成的;程序的生成过程即编译+链接;编译=将函数/变量等变成.o二进制的机器码格式;链接=将各个独立分开的二进制的函数链接起来形成整体的二进制可执行程序。
(2)编译以文件为单位+链接以工程为单位;编译器工作时是将所有源文件依次读进来并以单个为单位进行编译的;链接时是把第1步编译生成个单个的.o文件整体的输入,然后处理链接成1个可执行程序。
(3)外连接+内链接+无链接;外连接即外部链接属性,则其可以在整个程序范围内(跨文件)进行链接,譬如普通的函数和全局变量属于外连接;内链接即内部链接属性(c文件内部),则其可以在当前c文件内部范围内进行链接(不能跨文件),譬如static修饰的函数/全局变量属于内链接;无连接即该符号本身不参与链接,其跟链接没关系,譬如所有的局部变量(auto/static)都是无连接的。
(4)函数和全局变量的同名冲突;因为函数和全局变量是外部链接属性,即每个函数和全局变量将来在整个程序中所有的c文件都能被访问,则在单个程序中的所有c文件中不能出现同名的函数/同名的全局变量;最简单的解决方案即命名时不要重复,但很难做到,主要原因是某个庞大的工程中函数和全局变量名字太多了,而且大工程是由很多人协作完成,所以很难保证不会重名。
(5)现代高级语言中的解决方案是命名空间namespace(即给每个变量带上各个级别的前缀);C语言的解决方案即三种链接属性,即我们将明显不会在其他c文件中引用(只在当前c文件中引用)的函数/全局变量使用static修饰使其成为内链接属性,则将来连接时即使2个c文件中有重名的函数/全局变量,只要其中1个或2个为内链接属性就正常,该解决方案没有从根本上解决问题,所以导致C语言写很大型项目的难度非常大。
(6)static的第2种用法是修饰全局变量和函数,普通的(非静态)的函数/全局变量默认的链接属性是外部的,static(静态)的函数/全局变量的链接属性是内部链接。static修饰局部变量时会改变其存储类型,staic修饰全局变量/函数时会改变其链接属性。


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


0 0