1.linux下C语言程序的内存映像代码段(.text)、数据段(.data)、bss段、栈、堆的概念
代码段(.text) |
(1)对应着程序中的代码(函数),代码段在linux中又叫文本段(.text) (2)部分平台下的const修饰的变量。 |
数据段(.data) |
1、显式初始化为非0的全局变量; 2、显式初始化为非0的static局部变量 |
bss段 |
1、显式初始化为0或者未显式初始化的全局变量; 2、显式初始化为0或未显式初始化的static局部变量。 |
栈 | 局部变量分配在栈上;函数调用传参过程也会用到栈 |
堆 | 用时需要手工申请。 |
文件映射区 | 文件映射区就是进程打开了文件后,将这个文件的内容从硬盘读到进程的文件映射区,以后就直接在内存中操作这个文件,读写完了后在保存时再将内存中的文件写到硬盘中去。 |
内核映射区 |
(1)内核映射区就是将操作系统内核程序映射到这个区域了。 (2)对于linux中的每一个进程来说,它都以为整个系统中只有它自己和内核而已。它认为内存地址0xC0000000以下都是它自己的活动空间,0xC0000000以上是OS内核的活动空间。 (3)每一个进程都活在自己独立的进程空间中,0-3G的空间每一个进程是不同的(因为用了虚拟地址技术),但是内核是唯一的。 |
2.存储类相关的关键字
(1)讲述存储类关键字auto、static、register,其中重点是static。
auto关键字在C语言中只有一个作用,那就是修饰局部变量。默认就是auto
static两种用法:
第一种修饰局部变量为静态局部变量。
1、静态局部变量在存储类方面和全局变量一样。(数据段或bss段)
2、静态局部变量在生命周期方面和全局变量一样。(整个程序)
3、静态局部变量和全局变量的区别是:作用域、连接属性。静态局部变量作用域是代码块作用域(和普通局部变量是一样的)、链接属性是无连接;全局变量作用域是文件作用域(和函数是一样的)、链接属性方面是外连接。
第二种是修饰全局变量,函数名。表示只在当前文件下有用,用于避免重名。(修饰后的链接从外连接变成内链接)
register:用register修饰的变量编译器优先分配到寄存器中。
(2)讲述存储类关键字extern、volatile、restrict、typedef,其中重点是extern和volatile。
extern:在声明中用,表示这个变量在其他文件中定义。
volatile:比较敏感,可能被外界环境改变的量。用这个修饰后可以避免编译器过度优化。
typedef:
3.作用域
1、局部变量的代码块作用域
(1)代码块基本可以理解为一对大括号{}括起来的部分。
(2)代码块不等于函数,因为if while for都有{}。所以代码块<=函数
(3)局部变量的作用域是代码块作用域,也就是说一个局部变量可以被访问和使用的范围仅限于定义这个局部变量的代码块中定义式之后的部分。
2、函数名和全局变量的文件作用域
(1)文件作用域的意思就是全局的访问权限,也就是说整个.c文件中都可以访问这些东西。这就是平时所说的局部和全局,全局就是文件作用域。
(2)详细准确的说:函数和全局变量的作用域是定义所在的整个.c文件之内定义式之后的部分。
3、同名变量的掩蔽规则
(1)问题:编程时,不可避免会出现同名变量。变量同名后不一定会出错。
(2)首先,如果两个同名变量作用域不同且没有交叠,这种情况下同名没有任何影响。
(3)其次,如果两个同名变量作用域有交叠,C语言规定在作用域交叠范围内,作用域小的一个变量会掩蔽掉作用域大的那个(县官不如现管)。
4.生命周期
栈变量的生命周期
(1)局部变量(栈变量)存储在栈上,生命周期是临时的。临时的意思就是说:代码执行过程中按照需要去创建、使用、消亡的。
(2)譬如一个函数内定义的局部变量,在这个函数每一次被调用时都会创建一次,然后使用,最后在函数返回的时候消亡。
堆变量的生命周期
(1)首先要明白:堆内存空间是客观存在的,是由操作系统维护的。我们程序只是去申请然后使用然后释放。
(2)我们只关心我们程序使用堆内存的这一段时间,因此堆变量也有了自己的生命周期,就是:从malloc申请时诞生,然后使用,直到free时消亡。
数据段、bss段变量的生命周期
(1)全局变量的生命周期是永久的。永久的意思就是在程序被执行时诞生,在程序终止时消亡。
(2)全局变量所占用的内存是不能被程序自己释放的,所以程序如果申请了过多的全局变量会导致这个程序一直占用大量内存。
(3)如果说堆内存是图书馆借的书,那么全局变量就是自己买的书。
代码段、只读段的生命周期
(1)其实就是程序执行的代码,其实就是函数,它的生命周期是永久的。不过一般代码的生命周期我们并不关注。
(2)有时候放在代码段的不只是代码,还有const类型的常量,还有字符串常量。(const类型的常量、字符串常量有时候放在rodata段,有时候放在代码段,取决于平台)
5.链接属性
1、C语言程序的组织架构:多个C文件+多个h文件
(1)庞大、完整的一个C语言程序(譬如linux内核、uboot)由多个c文件和多个h文件组成的。
(2)程序的生成过程就是:编译+链接。编译是为了将函数/变量等变成.o二进制的机器码格式,链接是为了将各个独立分开的二进制的函数链接起来形成一个整体的二进制可执行程序。
2、编译以文件为单位、链接以工程为单位
(1)编译器工作时是将所有源文件依次读进来,单个为单位进行编译的。
(2)链接的时候实际上是把第一步编译生成个单个的.o文件整体的输入,然后处理链接成一个可执行程序。
3、三种链接属性:外连接、内链接、无链接
(1)外连接的意思就是外部链接属性,也就是说这家伙可以在整个程序范围内(言下之意就是可以跨文件)进行链接,譬如普通的函数和全局变量属于外连接。
(2)内链接的意思就是(c文件内部)内部链接属性,也就是说这家伙可以在当前c文件内部范围内进行链接(言下之意就是不能在当前c文件外面的其他c文件中进行访问、链接)。static修饰的函数/全局变量属于内链接。
(3)无连接的意思就是这个符号本身不参与链接,它跟链接没关系。所有的局部变量(auto的、static的)都是无连接的
4、函数和全局变量的同名冲突(static修饰)
(1)因为函数和全局变量是外部链接属性,就是说每一个函数和全局变量将来在整个程序中所有的c文件都能被访问,因此在一个程序中的所有c文件中不能出现同名的函数/同名的全局变量。
(2)最简单的解决方案就是起名字不要重复,但是很难做到。主要原因是一个很大的工程中函数和全局变量名字太多了,而且一个大工程不是一个人完成的,是很多人协作完成,所以很难保证不会重名。解决方案呢?
(3)现代高级语言中完美解决这个问题的方法是命名空间namespace(其实就是给一个变量带上各个级别的前缀)。但是C语言不是这么解决的。
(4)C语言比较早碰到这个问题,当时还没发明namespace概念,当时C语言就发明了一种不是很完美但是凑活能用的解决方案,就是三种链接属性的方法。
(5)C语言的链接属性解决重名问题思路是这样的:我们将明显不会在其他c文件中引用(只在当前c文件中引用)的函数/全局变量,使用static修饰使其成为内链接属性,这样在将来连接时即使2个c文件中有重名的函数/全局变量,只要其中一个或2个为内链接属性就没事。
(6)这种解决方案在一定程度上解决了问题。但是没有从根本上解决问题,留下了很多麻烦。所以这个就导致了C语言写很大型的项目难度很大。
5、static的第二种用法:修饰全局变量和函数
(1)普通的(非静态)的函数/全局变量,默认的链接属性是外部的
(2)static(静态)的函数/全局变量,链接属性是内部链接。
最后的总结
(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函数的链接属性为无连接。