c语言有32个关键字,每个关键字你都理解吗?
今天出场的是:
auto , register, static, extern
为什么他们会一起呢,说到这里不得不谈到c语言对变量的描述。
c给每个变量3个基本属性:
1. 作用域
2. 生命期
3. 存储类型
一些基本概念定义:
块的定义:
在c语言里 : 是以{}定义的 很显然函数是其中一个。
声明和定义:
1 声明是说有这个东西,但并不是马上要产生出来,比如你跟别人说你会玩dota,但你现在并没有玩。理解这个就好理解定义了。
注意: 声明可以在多个地方, 而这些地方就是所谓的命名空间(c语言没有这个概念,只是比喻),如果要引用全局变量,而又不在全局变量的作用范围内,可以用extern关键字,表示我要引用这个变量,他已经在某个文件里或其他全局区域定义了,还有就是没有定义,我也可以用,但link时候如果没有定义就会报错,无法解析(这个问题要牵扯到编译器的链接原理,在以后会有分析)。
2 定义就是产生它,一般我们都是声明和定义一起的,全局变量都是定义在文件的最上面,所以没有将声明和定义分开,分开的情况是在,多个文件编译中,如果其中一个(如果是头文件)文件定义了一个全局变量a,那么你引用这个头文件时(所谓的头文件包含,就是把头文件的东西copy到你的.c文件上面,只是方便浏览源代码才有头文件概念,一次定义,可以多处使用),很显然,你的.c文件再定义一个就是重复定义了。还有一种情况是: 在一个.c文件里定义了一个全局变量int a,如果你又在其他.c文件里定义一个全局变量int a,link 时( 链接)就会出错,如果是同名但不通类型,链接正常(编译器可以区分他们,这个问题要牵扯到编译器的链接原理,在以后会有分析)。
注意: 定义只能定义一次,不能多次,如果要问为什么,那是因为定义就是给变量分配内存空间(按变量的类型分配大小),所以只能定义一次。
作用域: 一个变量,就像当官一样,也有自己的领地,比如全局变量(就是从定义地方到文件结束),但是在某个块的内部,如果也定义了一个同名的变量(只可以定义一个),那这个地方就归他管了,你没有权利,他就是这个地方的皇帝,然而如果在其他块中没有与你同名的变量,那这些地方就由你管。看下面的图知道, 如果不是在函数内定义的变量,都是全局变量,其实全局变量区,就是各个函数之间的区域。全局是向下扩展的,并不是整个文件,就是说,在某个定义全局变量的上面,已经没有他的存在。如果上面的要访问他,可以用extern关键字(声明),这些对于局部变量行不通,对于局部变量在c语言里都必须定义在最前面。
要理解生命期先知道下面的。
看一下源代码文件的结构:
图 1
上面这个图配合下面概念讲的。
全局变量和局部变量, 你真的理解他们吗?其实谈到这个,又得讲一下c语言内存分配:
系统内核为高2GB,用户为低2GB(在32位的内存中)
图 2
BSS: 是“Block Started by Symbol”的缩写,意为“以符号开始的块”,在程序开始之前,内核将此段初始化为0。既这片内存在系统启动之后本身初值为0。
在c语言里 内存可分为静态存储区和动态存储区。
静态存储区: 就是程序在编译时就已经分配了,比如,全局变量(全局静态变量),静态局部变量等,且初始化是在程序启动开始时就已经初始化好了,对于这点,举个例:
void Fun()
{
static int a = 10; /*其实这个语句在函数运行时并没有执行,它是提供给系统(编译 器)一个初值10,然后再程序启动之前,把此变量初始化。*/
}
动态存储区: 程序在运行时,根据需要才分配的,比如,用户用malloc等动态申请的内存(系统堆,但用户控制),系统控制的函数调用栈。
所以,
全局变量: 就是定义在函数外的,且内存在静态存储区,作用范围:定义他的位置到文件结束。
局部变量:定义在块内(函数是块的最高境界),分为普通的局部变量(就是在栈中),和静态局部变量(在静态存储区),作用范围:定义它的位置到块结束。
注意: 很多人会不知道,什么时候用局部变量,什么时候用全局变量,少用全局变量,多用局部变量。全局变量是放在静态存储区中,定义了全局变量,系统就少了一个可以利用数据存储空间,太多全局变量,会导致编译器无足够内存分配;而局部变量,可以在不同的模块中重用(合理分配),其次从模块的耦合性来说,全局变量使模块的耦合性增加,模块独立性不高,还有在多线程程序里面,一般都建议不要用全局变量,这样可能造成线程访问冲突,加锁等很多问题,但这个东西是相对的,有时用全局变量已有好处,比如全局变量的空间范围一般比函数栈空间要大(和编译器有关),有时可能栈要溢出。如果函数要反复经常调用,函数里面有很多的局部变量,这些变量就可能要求栈反复地申请和释放,这样也耗时,所以这个问题要自己去体会,根据实际情况分析。
生命期: 就是一个变量内存分配到内存释放(我们称为死亡)的时间段。全局变量和局部静态变量一般是和程序共存亡,普通的局部变量一般是和块共存亡。
注意:虽然局部静态变量离开他的块时是不能访问的,但他并没有死亡,我们可以通过其他手段来访问它(嘿嘿)。
存储类型: 这个后面会有详细的讲解,现在只是说一下,其实在32系统中(其他可以类推),系统内存就是一个数组,每个地址对应一个字节。然后,为了让变量掌握的字节个数不同(提供更多给接口方便我们使用),系统为了区分它们,就给他们取了不同的名字(char,short,int,long等),然后在用的时候,就从它们的起始地址取出所占的字节数,然后再按一定语意来解析它们(之前协商好的协议(自己的理解))。
好,现在来具体讲哈
auto , register, static, extern
4个关键字
auto: 从名字上,我们称为自动变量,一般都叫局部变量,变量的默认属性就是auto,所以一般都没有用。
register: 就是定义寄存器变量,此变量有个例外,就是如果他真的称为寄存器变量,那么他存在于寄存器中,就不是在内存中,所以没有地址。此关键字会让系统尽量合适地把此变量作为寄存器变量(为什么不是一定、而是尽量,原因有很多,比如寄存器有限等),目的是提高访问速度。当一个变量被频繁读/写时,需要反复访问内存,花费大量存取时间。为了提高访问效率,可以使用CPU寄存器变量,不需要访问内存,直接进行读/写,这个关键字在嵌入式程序设计中经常会用到。
注意:
1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,因此凡需要采用静态存储方式变量都不能定义为寄存器变量。
但是使用register修饰符有几点限制
2) register变量必须是能被CPU所接受的类型。
这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
3)因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。
4)由于寄存器的数量有限(不同的类型cpu(Intel系列,ARM系列,PowerPC系列等)寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
5) 早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充(由于历史原因)。然而,随着编译程序设计技术的进步,在决定哪些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。
static: 此关键字很重要
对于变量:
1 static的变量存储在静态区,所以变量的值,有持久性,不会随着块的离开而消失;
2 变量被隐藏,如果是全局变量(此针对于多个文件)可见性就是定义他那个文件,其他文件不可访问,不可以用extern来引用,反过来就是说,如果没有static关键字,其他文件就可以通过extern来引用;
3 变量没有定义初值,系统(编译器)会默认设置初值为0;
对于函数: 没有static修饰的函数,在多个文件编译时,具有全局可见性(这个读者自己可以试试)而有static修饰的函数,只是在本文件可见,对其他文件隐藏。
extern : 上面很多地方提到, 对于全局变量如果不在作用域内,extern 可以扩大他的作用域。例如,在单个文件中,在全局变量之上地方可以用extern来引用下面定义的全局变量,而在多个文件中,extern可以引用其他文件的全局变量。
注意: extern能否引用其他文件的全局变量,要看其他文件的全局变量是否隐藏,很显然局部变量是不能引用的。
声明:
本文完全是为了学习而诞生的, 我们只有不断理解,不断地从错误的认识中清醒过来,才可能更好使用它,希望对你们有用,要成为顶级的c程序员这些只是,记住只是基础中的基础,还有很多的东西有待我们去研究实践,这路还很长。
思考小问题:
c语言里的一个递归问题,怎么从第20层直接返回到第17层,保证程序正常运行?