4.7.1概念解析
4.7.1.一、存储类
- (1)存储类就是存储类型,也就是描述C语言变量在何种地方存储
- (2)内存有好多中管理方法:栈、堆、.data段、.bss段、.text段..........一个变量的存储类属性就是描述这个变量存储在哪一个内存段中。
- (3)譬如:局部变量分配在栈上,因此它的存储类是栈;显示初始化为非0的全局变量分配在数据段。显示初始化位0和没有显式初始化(默认是0)的全局变量分配在.bss段。
4.7.1.二、做用域
(1)做用域是描述这个变量起做用的代码范围html
(2)基原本说,C语言的做用域规则是代码块做用域。意思是这个变量其做用的范围是当前的代码块。代码块就是一堆大括号{ }范围内,因此这个变量的做用域是:这个变量定义所在的 { } 内从这个变量定义开始日后的部分(这就解释了为何变量定义老是在函数的最前面)linux
#include <stdio.h>
int var = 10;
int main(void)
{
int var = 5;
if(1)
{
int var = 2;
printf("var = %d.\n",var);
}
printf("var = %d.\n",var);
return 0;
}
运行结果:
var = 2.
var = 5.
4.7.1.三、生命周期
- (1)生命周期是描述这个变量何时诞生(运行时分配内存空间给这个变量)以及何时死亡(运行时收回这个内存空间,伺候再不能访问这个内存地址,或者这个内存地址已经和这个变量无关了)
- (2)变量和内存的关系,就和人(变量)去图书馆(DRAM)借(申请)书(内存)同样。变量的生命周期就好像人借书的这段周期同样。
- (3)研究变量的生命周期能够帮助咱们理解程序运行的一些现象,理解C语言的一些规则。
4.7.1.四、连接属性
- (1)你们知道程序从源代码到最终可执行程序,经历的过程:编译、连接
- (2)编译阶段就是把源代码编译成.o目标文件,目标文件里有不少符号和代码段、数据段、bss段等分段。符号就是编程中的变量名函数名等。运行时,变量名和函数名可以和相应的内存对应起来,靠符号来作链接的。
- (3).o的目标文件连接生成最终可执行程序的时候,其实就是把符号和相对应的段链接起来。C语言中的符号有三总连接属性:外连接属性、内链接属性、无连接属性
总结:以上4个概念,其实就是从4个不一样角度分析C语言的一些规则。综合这4个分析角度,可以让程序员彻底掌握C语言程序的运行规则和方法。程序员
4.7.2linux下C程序的内存映像
4.7.2.一、代码段、只读数据段
- (1)对应着程序中的代码(函数),代码段在linux中又叫文本段(.text)。
- (2)只读数据段就是在程序运行期间只能读不能写,const修饰的常量就有多是存放在只读数据段的(可是不必定的,const关键字在不一样的平台实现方法是不同的)
4.7.2.2数据(.data)段、bss段
- 数据段:显示初始化为非0的全局变量、显示初始化为非0的static局部变量。
- bss段:显示初始化为0或者未显示初始化的全局变量,显示初始化为0或者维显式初始化为0的static局部变量。
4.7.2.三、堆
- (1)C语言中什么变量存在堆内存中?C语言不会自动向堆内存中存放东西,堆的操做是程序员本身手工操做的。程序员根据需求本身判断要不要使用堆内存,用的时候本身申请,本身使用,使用完本身释放。
4.7.2.四、文件映射区
- (1)文件映射区就是进程打开文件后,将这个文件的内容从硬盘的内容读到进程的文件映射区,之后就直接在你的内存中操做这个文件,读写完了之后在保存时再将内存中的文件写到硬盘中。
4.7.2.五、栈
- (1)栈内存区:局部变量分配在栈上;函数调用传参过程也会用到栈。
4.7.2.6内核映射区
- (1)内核映射区就是将操做系统内核程序映射到这个区域了
- (2)对于linux中的每个进程来讲,它都觉得整个系统中只有它本身和内核而已,它认为内存地址0xC0000000如下都是它本身的活动空间,0xC0000000以上是操做系统(OS)内核的活动空间
- (3)每个进程都活在本身独立的进程空间中,0~3G的空间每个进程是不一样的(由于用了虚拟地址技术),可是内核是惟一的。
4.7.2.七、OS下和裸机C程序加载执行的差别
- (1)C语言运行时环境有必定要求,意思是单独我的写的C语言程序无法直接在内存中运行,须要外部必定的协助,这段协助代码叫作加载运行代码(或者说构建C语言运行时环境代码,这一段代码是操做系统下被人写好的,会自动添加到咱们写的程序上,这段代码的主要做用是:给全局变量赋值,清bss段)
- (2)ARM裸机十六部分,写shell时 有一次定义了全局变量初始化为0了,可是实际上不为0,后来在裸机的start.S中加了清bss段就变0 了。这就说明了在裸机程序中没人帮咱们作这一段程序运行时代码,要程序员本身作(start.S中的重定位和清bss段就是作这个事),在操做系统中,运行程序时,程序员本身不用操心,会自动完成重定位和清bss段,因此咱们看到的现象是:C语言中初始化的全局变量默认为0.........。
- (3)数据段的全局变量或静态局部变量都是有非0的初值,在main函数运行以前就已经被初始化了。是重定位期间完成的初始化。
4.7.三、存储类相关的关键字1
4.7.3.一、auto
- (1)auto关键字在C语言中只有一个做用。修饰局部变量
- (2)auto修饰局部变量,表示这个局部变量是自动局部变量,自动局部变量分配在栈上。(既然分配在栈上,说明它不初始化那么值就是随机的)
- (3)平时定义局部变量时就是定义auto的,知识省略了auto关键字。可见,auto的局部变量其实就是默认定义的普通的局部变量
4.7.3.二、static
- (1)static关键字在C语言中有两种用法,并且在C语言中没有关联。彻底独立的。其实当年本应该多发明一个关键字的,可是做者以为关键字太多很差记因此给static多了一种用法,致使static一个关键字有两种含义
- (2)static的第一种用法:修饰局部变量,造成静态局部变量。要搞清楚静态局部变量和非静态局部变量的区别。本质区别是存储类不一样(存储类不一样就衍生出不少不一样)非静态局部变量分配在栈上,而静态局部变量分配在数据段或者是bss段上。
- (3)static 的第二种用法:修饰全局变量,造成静态全局变量。要搞清楚静态全局变量和非静态全局变量的区别,区别主要在连接属性上不一样,在连接属性处详细讲。
分析:shell
- 一、静态局部变量在存储类方面和全局变量同样
- 二、静态局部变量在生命周期方面和全局变量同样
- 三、静态局部变量个全局变量的区别是:做用域、连接属性。静态局部变量做用域是代码块做用域,和普通局部变量是同样的。无链接属性。而全局变量的做用域是文件做用域和函数是同样的、连接属性是外连接。
4.7.3.三、registe
- (1)register关键字不经常使用,也只有一个做用,那就是:register修饰的变量编译器会尽可能将他分配在寄存器中。(平时分配的局部变量都是分配在栈即内存中的)。分配在寄存器中同样的用,可是读写效率会高不少。因此register修饰的变量用在哪些变量被反复使用,经过改善这个变量的访问效率能够极大的提升程序运行的效率时。因此register是一种极致的提高程序运行效率的手段。
- (2)uboot中用到了一个registe类型的变量,gd这个变量是用来存uboot中的全局变量(gd就是global data)由于这个全局变量在uboot中导出都被访问,因此定义成register的
- (3)平时写代码用到register的状况不多,通常慎用。
- (4)register编译器只是承诺尽可能将register修饰的变量放在寄存器中,可是不保证必定放在寄存器中,主要缘由是寄存器是有限的,不必定有空的给你用。
4.7.4存储类相关的关键字2
4.7.4.一、extern
- (1)extern主要是用来声明全局变量,声明的目的主要是在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关键字。
4.7.4.二、volatile
- (1)volatile的字面意思:可变的、易变的。C语言中用来修饰一个变量表示这个变量能够被编译器以外的东西改变。编译器之类的意思是变量的值的改变是代码的做用,而编译器以外的改变是指这个改变不是代码形成的,或者说不是当前代码形成的,编译器在编译当前代码时没法预知。
- 譬如说在中断处理程序isr中,更改的这个变量的值,
- 譬如多线程中更改的这个变量的值
- 譬如说硬件自动更改的这个变量的值(通常是寄存器)。
- (2)以上说的三种状况:中断isr中引用的变量、多线程中共用的变量,硬件会改的变量
- 编译器在编译是没法预知的更改,此时应该使用volatile告诉编译器这个变量属于这种(可变的、易变的)状况。编译器在遇到volatile修饰的变量时就不会对这个变量的访问进行优化,就不会出现错误。并且这种错误发生了不容易找到。
- (3)编译器的优化在通常状况下很是好,能够帮助提高程序效率,可是在特殊状况下,变量会背编译器想象以外的力量所改变,此时若是编译器没有意识到,而进行了优化则就会形成错误。优化错误就会形成执行错误。
- (4)volatile是程序员意识到须要volatile而后再定义时加上volatile,若是你遇到了须要加volatile的状况却没有加,程序可能会出现错误。若是在不须要加volatile的状况加了volatile程序不会出错,只是会下降程序的效率。因此对于volatile的正确态度是该加就加,不应加就不加,若是不能肯定就加。
4.7.4.三、restrict
- (1)C99中才支持的,因此不少延续C89的编译器是不支持register关键字,gcc支持的。
- (2)restrict也是和编译器行为特征有关的。
- (3)resrtict只用来修饰指针,不能修饰普通变量。
- (4)http://blog.chinaunix.net/uid-22197900-id-359209.html
- (5)memcpy和memmove的区别
4.7.4.四、typedef
- (1)以前讲过了。
- (2)typedef在C语言关键字归类上属于存储类关键字,可是实际上和存储类不要紧。
4.7.5做用域详解
4.7.5.一、局部变量的代码块做用域
- (1)代码块基本能够理解成一对大括号{ }栝起来的部分
- (2)代码块不等于函数,由于if while for 都有{ },因此代码块<=函数
- (3)局部变量的做用因而代码块做用域,也就是一个局部变量能够被访问和使用的范围仅限于定义这个局部变量的代码块中定义了变量以后的部分。
4.7.5.二、函数名和全局变量的文件做用域
- (1)文件做用域的意思是全局的访问权限,也就是在整个的.c文件中均可以访问这些东西。这就是平时所说的局部和全局,全局就是文件做用域。
- (2)详细准确地来讲就是:函数和全局变量的做用域就是定义所在的整个.c文件以内定义式以后的部分。
总结:编程
- (1)无论是局部变量、全局变量、函数,都要先定义才能使用。
- (2)严格来讲咱们上面的总结是错的,准确的说全局变量的做用域都是本身所在的代码块/文件,可是定义式以前的部分由于咱们缺乏声明,因此无法在定义以前用。解决方案:一:把他定义到前面去 二:定义到后面可是在前面加声明。局部变量因为无法声明,因此只能定义到前面去。
- (3)在c89标准的编译器中(如今不少的编译器还在沿用c89),全部的局部变量必须定义在最前面,在变量定义前,不能有一句执行代码。在c99标准的编译器中(gcc兼容c99标准 )能够容许在代码块任意地方定义变量,可是容许定义的变量仍是只能使用在定义以后,定义以前仍是不能用的。
4.7.5.三、同名变量的掩蔽规则
- (1)问题:编程时,不可避免会出现同名变量,变量同名后不必定会出错
- (2)若是两个同名变量的做用域不一样且没有交叠,这种状况下没有任何影响。
- (3)若是两个同名变量做用域有交叠,C语言在做用域的交叠范围内,做用域小的一个变量会掩蔽掉大的那个(县官不如现管)
4.7.六、变量的生命周期
4.7.6.一、研究变量生命周期的意义多线程
(1)研究变量的生命周期,有助于理解变量的行为特征。架构
4.7.6.二、栈变量的生命周期
- (1)局部变量(栈变量)存储在栈上,生命周期是临时的,临时的意思是说:代码执行过程当中按照须要去建立、使用、消亡的。
- (2)譬如一个函数内定义的局部变量,在这个函数每一次被调用时都会被建立一次,而后使用,最后在函数返回时消亡。
- (3)思考:一个函数内的局部变量为何在函数外不能使用?
- (4)思考:局部变量为何分配在栈上?局部变量为何是临时生命周期?
4.7.6.三、堆变量的生命周期
- (1)首先要明白:对内存空间是客观存在的,由操做系统维护的,咱们程序只是去申请而后使用而后释放。
- (2)咱们只关心咱们程序使用堆内存的这段时间,所以堆内存也有本身的生命周期,就是:从malloc申请时开始,而后使用,知道free时消亡。
- (3)因此堆内存在malloc以前和free以后都不能够访问,所以堆内存在实践编程时都是反复malloc和free的
4.7.6.4数据段、bss段变量的生命周期
- (1)全局变量的生命周期是永久的。永久的含义是在程序执行时诞生,在程序终止时消亡
- (2)全局变量所占用的内存是不能被程序本身释放的,因此程序若是申请了过多的全局变量,会致使这个程序一直占用大量的内存。
- (3)若是说堆内存是图书馆借书,那么全局变量就是本身买书。
4.7.6.5代码段、只读段的生命周期
- (1)其实就是程序执行的代码,其实就是函数,他的生命周期是用永久的。不过通常代码的生命周期咱们并不关注
- (2)优点后放在代码段的不仅是代码还有const类型的常量,还有字符串常量有时候放在rodata段,有时候放在代码段,取决于平台。
4.7.7连接属性
4.7.7.1C语言程序的组织架构:多个C文件+多个h文件
- (1)庞大、完整的一个C语言程序(譬如linux内核、uboot)由多个c文件和h文件组成的
- (2)程序的生成过程就是:编译+连接。编译是为了把函数/变量编译成.o二进制的机器码格式。连接时为了将各个独立分开的二进制的函数链接起来造成一个总体的二进制可执行程序。
4.7.7.二、编译以文件为单位、连接以工程为单位
- (1)编译器工做时是将全部源文件一次读进来,单个为单位进行编译的。
- (2)连接时其实是把第一步编译生成的单个的.o文件总体的输入,而后处理连接成一个可执行程序。
4.7.7.三、三种连接属性:外连接、内连接、无连接
- (1)外连接的意思是外部连接属性,也就是这家伙能够在整个程序范围内(言下之意就是能够跨文件)进行链接,譬如函数和全局变量属于外连接
- (2)内连接的意思就是(C文件内部)内部连接属性,这家伙能够在当前的C文件内部范围内进行连接。(言下之意就是不能在该在当前c文件外部的C文件进行访问和连接)static修饰的函数和全局变量都是内连接。
- (3)无连接的意思就是这个符号自己不参与连接,连接的时候根本不用考虑他。他跟连接没有关系,全部的局部变量(auto的、static的)都是无连接。
4.7.7.五、函数和全局变量的同名冲突
- (1)由于函数和全局变量是外部连接属性,就是说每个函数和全局变量,未来在整个程序中全部的C文件都能被访问。所以,在一个程序中的全部的C文件中不能出现同名函数/同名的全局变量
- (2)最简单的解决方案就是起名字不要重复,可是很难作到,主要缘由是一个很大的工程中函数和全局变量的名字太多了,并且一个大工程不是一我的完成的,是不少人协助完成的,因此很难保证不重名,解决方案?
- (3)线代高级语言汇总完美解决了这个问题的方法是命名空间(其实就是给一个变量带上各个级别的前缀)可是C语言不是这么解决的
- (4)C语言比较早碰到这个问题,当时还没发明namespace概念,当时C语言发明了一种不是很完美的可是凑合能用的解决方案,就是三种连接属性的方法。
- (5)C语言的连接属性解决重名问题的思路是这样的,将明显不会再其余的C文件中引用的(只有在当前C文件中引用)的函数/全局变量,使用static修饰使其成为内连接属性,这样即便2个C文件中有重名的函数/全局变量,只要其中一个或者两个为内连接属性就没事。
- (6)这种方法在必定程度上解决了问题,可是没有从根本上解决问题,留下了不少麻烦。因此这个就致使了C语言写很大型的项目难度很大。
4.7.7.五、static的第二种方法:修饰全局变量和函数
- (1)普通的(非静态的)函数/全局变量,默认的连接属性是外连接
- (2)static(静态的)函数/全局变量,连接属性是内部连接
4.7.7.六、通常用法总结:
思考:为何static一个管家你这能够有两种不一样的意思?函数
由于这两种用法是互斥的。就是说static一个是修饰全局变量,一个是修饰局部变量,因此不会引发歧义。优化
4.7.八、最后的总结
- (1)普通(自动)局部变量分配在栈上,做用域位代码块,生命周期是临时的,连接属性无连接。定义时若是未显示初始化则其值是随机的。变量地址由运行时在栈上分配获得,屡次执行时地址不必定相同,函数不能返回该类变量的地址(指针)做为返回值。
- (2)静态局部变量分配在数据段/bss段(显式初始化非0则在数据段,不然在bss段),做用域为代码块做用域,生命周期为永久,连接属性:无连接。定义时若是未显示初始化其值为0,变量地址由运行时环境在加载程序是肯定,整个程序运行过程当中惟一不变;静态局部变量其实就是做用域为代码块做用域(同时连接属性为无连接)的全局变量。静态局部变量能够改成用全局变量实现(程序中尽可能避免使用全局变量,由于会破坏结构性)
- (3)静态全局 变量/静态函数和普通全局变量/普通函数的区别在于:static使全局变量/函数的连接属性由外部连接(整个程序全部文件范围内)改成内部连接(当前c文件)。这是为了解决重名问题(C语言没有命名空间name space的概念,所以在程序中文件变多以后全局变量/函数的重名问题很是严重,将没必要要被其余文件引用的全局变量/函数声明为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函数的连接属性为无连接