|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
大家好,我们今天来探讨一下C语言的全局变量和局部变量。如果我们想彻底搞清楚这两个概念,我们必须回答几个问题:第一,C语言为什么要区分变量的作用域,或者说做出全局变量和局部变量的区别;第二,C语言如何定义全局变量和局部变量;第三个,全局变量和局部变量在单片机内部是如何存储的,为什么要这样存储以及如何查看他们的存储位置。4 Z$ L5 c# W- J8 s& ~0 z( o
) m4 ^1 C" U; m9 i! |
我们先来看第一个问题,C语言为什么要区分变量的作用域?这个问题也可以反过来问,能不能只有全局变量或者说局部变量呢,或者说只有全局变量和局部变量会有什么问题呢?
3 O+ P) ~! Z5 P
; l, D/ E$ t) [! p6 M1 Z+ }0 |我们先看全局变量,全局变量的好处是什么呢?它可以增加函数间发生联系的渠道,是吧;让函数与函数之间的交互变得更为方便,只需要在程序的执行过程中,去访问同一个地址空间的数据即可。函数之间的交互是不是就方便很多呀。但是过于方便也会有问题的,尤其是在一个复杂的多任务的系统中,如果各个程序都可以随意地更改这个变量的值,那么就有可能会发生程序紊乱。所以在操作系统中,比如RTOS中,对这种过度的灵活性就做出了一定的限制,新出来的一些全局变量的名称,比如信号量、互斥量、事件、邮箱、消息队列等等。这个事情,我们扯远了。但是这个东西,从本质上说明了全局变量的好处以及过度的灵活性带来的问题,还有我们在实际解决问题的过程中如何去限制这种过度的灵活性。8 h2 @: w( y: [. v& ^6 p% ~( v
' W, |& f8 i4 u% B6 |6 A1 C
我们下面看一下,为了这种灵活性,单片机付出了什么样的代价。我们是全局变量是常驻内存的,也就是说常驻RAM的。如果一个程序里面全部是全局变量,那对内存的占用量是很大的。那么从跑程序来看,成本就是非常高昂的,尤其是规模较大的程序,更是这样。
1 E7 l# [! t1 k- w$ m, G& G; O# D& e; N/ }' G M1 U1 `. y
而实际上,很多变量只是中间状态而已,没有必要让它常驻内存,使用一下就过去了。那么归结到内存地址上来说,也就是同一个地址,可以分时表示不同的数据。这里有点用时间换空间的意思。对于这种临时变量,我们就称之为局部变量。它的作用域只是某一个单独的函数,在CPU执行这个函数时候,它是存在的;等到函数执行结束,这个变量的生命周期也就结束了。局部变量的引入是不是极大提高了内存的使用效率呀,这个也就是局部变量存在的重要意义。
: Z- W& b+ h! [7 ?6 A& X2 I, H( j3 G9 U
从变量命名的角度看,局部变量可以和全局变量同名的,这样有局部变量的存在还可以节省一些命名资源。如果一个程序全部使用全局变量,如果程序比较大,变量的命名可能也是比较困难的问题。局部变量和全局变量名字相同,当它们有冲突的时候,在包含局部变量的函数内部,实际使用的是局部变量;如果一个函数内部没有定义这个局部变量,那么使用的就是全局变量。
! H0 M/ ~0 B0 s! q& @3 B. J
- Z' |# m# g! J#include <stdio.h>( O$ z7 _) i; R3 @) z
#include <stdlib.h>
0 p) C! q' r$ v: j( h3 E6 yint n=5;
0 ?% ^& `# @. g+ W: Uvoid fun(int m)
2 `: S- u! {7 ]{8 O+ `/ m) H" k% I/ g, X' E6 L- e
int n=1;, z% n) e* ~& J* }2 Z2 {# |" j6 a
if(m<10)8 U: J' H' ^& s! { p+ H. y
{/ ~# Q; q! v S7 ]9 y' l
fun(m+1);
9 S# j+ r( P E7 F, w' t printf("%d ",n++);
) B: @2 ~, C; h# w" N }! C. X/ B N4 a5 @& h& u% T5 @
: S n/ ^& I5 w K+ ~}<br>' ~! `, v, u" L( c* ^
void print()
8 ?; f% [: g1 l" Q* p{2 w( G9 _. l, z, y
printf("%d\n",n);* [. `# O( g8 T5 a$ u x
}<br><br>/*void printm()<br>{<br> printf("%d\n",m)<br>}*/<br>
4 v; \. u( c, t# f/ q* d6 Gint main()
! u1 N. {) G0 S8 D* g) m{* `! ^/ H) q9 J8 o% O' U
int n;
% l8 J+ W" H/ I* d1 ?. r0 {0 x scanf("%d",&n);
5 u6 k9 Y; _2 Z: j printf("%d\n",n);
7 j' U" _: O; y$ r print();
. q7 F/ e* [' P# p: j fun(n);<br> // int m=10;<br> // printm();0 E6 o( z1 P: G k6 A. |/ b
return 0;) W; U- j6 T) k l
}* \2 C' z8 {& `: o/ T$ w& U2 R
" J- W( C, {0 n4 ]! d0 @
这个程序的输出结果是:3,5,1,1,1,1,1,1,1。大家可以从这个函数的执行结果来分析一下局部变量和全局变量重名的时作用域的划分问题。
2 L8 i6 \" Z4 A* ^$ A
9 A5 y! Y$ q, g# v, v+ n7 l# E当然如果一个程序只有局部变量,那程序设计的复杂度就会非常高,所以要通过全局变量来增加函数之间相互联系的通道。那么总结下来,全局变量和局部变量一个硬币的两面,都是不可或缺的,是程序设计效率和内存使用效率方面的一个平衡。# |6 u" y! {9 ~7 g- I" i, x3 U: z
对于作用域的问题,C语言对此有更为精细的划分。与此相关的一个关键字,就是static。
8 T9 J7 L) H2 ~$ q- @ K- X3 G. Y, H7 j- j/ {
static这个字,如果放在全局变量前面,就限制了这个全局变量的使用范围,把它的通用性限定在了一个特性的文件内,那么也就是说这个全局变量只有在定义它的源文件内有效。
" T' R- y$ q$ W# s4 M. |% K* T4 ^
( ~$ g6 D: \. H& K) u- Ostatic放在局部变量前,会改变局部变量的存储位置,让这个局部变量在程序的整个生命周期都存在。对于这样的局部变量,我们也可以把它当成一种特殊的全局变量来理解,一种作用域只限定在一个函数内部的全局变量来理解。这样理解起来,貌似更为顺当。. d3 c2 M3 L5 R8 L2 Z7 R) A/ m
. S5 t- c+ q& c$ w2 Q9 F8 o7 [
下面,我们来看一下,C语言如何定义全局变量和局部变量。那么根据我们刚才的分析,其实全局变量和局部变量的定义就很容易分清楚。那就是在某一个函数内部定义的变量,就是局部变量,这个函数也包括main函数;独立于某一个函数的变量,就是全局变量。我们说全局变量的作用域是整个工程,那么在C语言的工程中,一个文件中的函数如何引用另外一个文件里面的全局变量呢?这里面有两者方式,一个是通过头文件引用的方式,一个是使用关键字“extern”。那这两种方式有什么不同呢?头文件引用的方式,相对来说比较容易出问题。
1 D+ f% |+ Y( L% R) i2 F$ j0 t. D( {3 I( d
具体的情形是在头文件中定义了变量,但是其他的源文件(比如超过两个)都引用了这个头文件,那么就会造成全局变量的重复定义。这个报错会在编译的时候就报错的。使用“extern”这个关键字,如果把全局变量的名字写错了,也会发生错误,不过这个错误是在链接的时候出错的。必须要说明的是,对于全局变量,只能有一次定义,但是使用“extern”关键字进行声明引用,却可以很多次。局部变量和全局变量,在初始化的时候,也有区别。全局变量在定义的时候,如果程序开发人员不进行显式初始化,编译器会默认把这个变量初始化成零。但是局部变量,编译器则不会对其默认初始化,具体原因可能是初始化成本比较高,而局部变量的生命周期相对来说比较短暂,不值得花费这个时间。编译器把这个工作交给了程序开发者自己选择。* ]2 T5 I" \' J. j% t
$ r9 l% n7 H7 v5 ]$ i! a8 A
# R! g3 P# Q! |" V1 I) Q$ @
最后,我们看一下局部变量和全局变量在内存中是如何存储的。要想搞清楚这个问题,那就得回答另外一个问题,内存管理的效率如何能够相对比较高。站在高级语言的角度上来看,一般内存可以分为动态存储器和静态存储区两个大类。动态存储区,分为堆和栈两类,栈由编译器自动分配和释放,存放函数的参数值、局部变量的值等。堆,则由程序员分配释放和释放,程序结束时,也有可能由OS回收。关于堆和栈的区别,我们后面再深入探讨。它们大体的区别在于堆分配的数据空间较大,主要是靠程序员自己来实现内存的分配和释放,那么自然就容易产生内存碎片以及内存泄露等问题。堆区在操作系统中使用得是比较多的。静态存储区则分为数据段和代码段,代码段相对来说比较好理解。处于静态存储区的数据段,细分下来也可以分为两类,一类是程序员没有初始化的(bss),一类是程序员已经初始化的。
. p# d3 A7 k" u% m7 U" G, U) z9 L
从典型的C语言内存分布图来看,他们内存的分流基本是按照分类的规则来的,基本上是同一类的东西放在一起,方便管理。比如像动态存储的栈和堆放在一起,一个向上生长,一个向下生长,这样内存出问题的可能性是最小的。另外数据段挨着放在一起,下面就是代码段了。
; P& n) V' f9 D- n! j0 A1 p1 G: I2 u& R) r" G
了解了C语言内存的大体分布,我们是不是自然就知道了局部变量和全局变量是存在在内存中的什么位置了呀。因为局部变量的生命周期是非常有限的,它是放在栈区的,放在动态存储区的。如果我们把static类型的局部变量也看成一种特殊的全局变量的话,它们都是放在静态存储区的。对于操作系统中使用到的数据结构,比如任务链表这种庞大的数据,一般是放在堆区的。它的生命周期是程序员自己管理的,里面的成员变量是动态变化的,那么它的位置介于栈和静态存储区之间。
8 A% C+ f1 j( ?; U3 n
; z6 U0 m4 S2 a7 w4 M( y( [& ^& n回答清楚了上面三个基本问题,我们对全局变量和局部变量的认识可以说是相对来说清楚了一些。
4 r7 L A4 Q ~9 t# }9 p+ p3 o c+ u/ E2 _6 {$ i# f V
' j& E8 e1 Z+ c' `2 R& K; Z+ V% e0 P0 y+ l5 C5 a) g
0 ]$ M) Y* }0 [# ^# _: G9 J
( x" f: w; y* l: D' z |
|