Chinaunix首页 | 论坛 | 博客
  • 博客访问: 257455
  • 博文数量: 58
  • 博客积分: 2241
  • 博客等级: 大尉
  • 技术积分: 522
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-07 09:53
文章分类
文章存档

2012年(4)

2011年(19)

2010年(31)

2009年(4)

分类:

2010-11-24 08:57:41

对于不同编译单位的全局变量,其初始化的顺序没有任何的保证,因此对不同编译单位里的全局变量,在它们的初始化顺序之间建立依赖性都是不明智的。
    此外也没办法捕捉到全局变量初始化抛出的异常,一般来说要减少全局变量的使用,特别是限制那些要求复杂初始化的全局变量。so:
    1,尽量不用全局变量 
    2,用静态变量,通过访问器进行访问 
例如:全局变量  
    int   a   =   5;  
    int   b   =   a;  
    如果a,和b定义在同一个文件里,那没什么问题,结果b等于5.  
    如果a和b定义在不同文件里,就不能保证b也等于5,也就是说不能保证a先初始化.
事实上,除了在同一个文件定义的全局对象的初始化是按照定义次序来进行的之外,其他全局或静态变量之间的初始化次序没有任何保障。解决这种问题的方法是不直接使用全局变量,而改用一个包装函数来访问,例如  
  int   get_a()  
  {  
          static   int   a   =   5;  
          return   a;  
  }  
  int   get_b()  
  {  
          static   int   b   =   get_a();  
          return   b;  
  }   
    
    这样的话,无论get_a和get_b是否定义在同一个文件中,get_b总是能够返回正确的结果,原因在于,函数内部的静态变量是在第一次访问的时候来初始化。 
    任何时候,如果在不同的被编译单元中定义了"非局部静态对象",并且这些对象的正确行为依赖于它们被初始化的某一特定顺序,就会产生问题.你绝对无法控制 不同被编译单元中非局部静态对象的初始化顺序.对于函数中的静态对象(即"局部"静态对象)它们在函数调用过程中初次碰到对象的定义时被初始化..  
    PS:千万不要写出和编译顺序相关的程序来。 
 
关于全局变量的初始化,C语言和C++是有区别的。      
   在C语言中,只能用常数对全局变量进行初始化,否则编译器会报错。       
   在C++中,如果在一个文件中定义了int a = 5;要在另一个文件中定义int b = a;的话,前面必须对a进行声明:extern   int   a;否则编译不通过.、即使是这样,int b = a;这句话也是分两步进行的:在编译阶段,编译器把b当作是未初始化数据而将它初始化为0;在执行阶段,在main被执行前有一个全局对象的构造过 程,int b = a;被当作是int型对象b的拷贝初始化构造来执行。    
   其实,准确地说,在C++中全局对象、变量的初始化是独立的,如果不是象int a   =   5;这样的已初始化数据,那么就是象b这样的未初始化数据。    
   而C++中全局对象、变量的构造函数调用顺序是跟声明有一定关系的,即在同一个文件中先声明的先调用。对于不同文件中的全局对象、变量,它们的构造函数调用顺序是未定义的,取决于具体的编译器。

全局变量、局部变量和作用域

我们把函数中定义的变量称为局部变量(Local Variable),由于形参相当于函数中定义的变量,所以形参也是一种局部变量。在这里“局部”有两层含义:

1、一个函数中定义的变量不能被另一个函数使用。例如print_time中的hourminutemain函数中没有定义,不能使用,同样main函数中的局部变量也不能被print_time函数使用。如果这样定义:

void print_time(int hour, int minute)
{
printf("%d:%d\n", hour, minute);
}

int main(void)
{
int hour = 23, minute = 59;
print_time(hour, minute);
return 0;
}

main函数中定义了局部变量hourprint_time函数中也有参数hour,虽然它们名称相同,但仍然是两个不同的变量,代表不同的存储单元。main函数的局部变量minuteprint_time函数的参数minute也是如此。

2、每次调用函数时局部变量都表示不同的存储空间。局部变量在每次函数调用时分配存储空间,在每次函数返回时释放存储空间,例如调用print_time(23, 59)时分配hourminute两个变量的存储空间,在里面分别存上23和59,函数返回时释放它们的存储空间,下次再调用print_time(12, 20)时又分配hourminute的存储空间,在里面分别存上12和20。

与局部变量的概念相对的是全局变量(Global Variable),全局变量定义在所有的函数体之外,它们在程序开始运行时分配存储空间,在程序结束时释放存储空间,在任何函数中都可以访问全局变量,例如:

例 3.5. 全局变量

#include 

int hour = 23, minute = 59;

void print_time(void)
{
printf("%d:%d in print_time\n", hour, minute);
}

int main(void)
{
print_time();
printf("%d:%d in main\n", hour, minute);
return 0;
}

正 因为全局变量在任何函数中都可以访问,所以在程序运行过程中全局变量被读写的顺序从源代码中是看不出来的,源代码的书写顺序并不能反映函数的调用顺序。程 序出现了Bug往往就是因为在某个不起眼的地方对全局变量的读写顺序不正确,如果代码规模很大,这种错误是很难找到的。而对局部变量的访问不仅局限在一个 函数内部,而且局限在一次函数调用之中,从函数的源代码很容易看出访问的先后顺序是怎样的,所以比较容易找到Bug。因此,虽然全局变量用起来很方便,但一定要慎用,能用函数传参代替的就不要用全局变量

如果全局变量和局部变量重名了会怎么样呢?如果上面的例子改为:

例 3.6. 作用域


则第一次调用print_time打印的是全局变量的值,第二次直接调用printf打印的则是main函数局部变量的值。在C语言中每个标识符都有特定的作用域,全局变量是定义在所有函数体之外的标识符,它的作用域从定义的位置开始直到源文件结束,而main函数局部变量的作用域仅限于main函数之中。如上图所示,设想整个源文件是一张大纸,也就是全局变量的作用域,而main函数是盖在这张大纸上的一张小纸,也就是main函数局部变量的作用域。在小纸上用到标识符hourminute时应该参考小纸上的定义,因为大纸(全局变量的作用域)被盖住了,如果在小纸上用到某个标识符却没有找到它的定义,那么再去翻看下面的大纸上有没有定义,例如上图中的变量x

到目前为止我们在初始化一个变量时都是用常量做Initializer,其实也可以用表达式做Initializer,但要注意一点:局部变量可以用类型相符的任意表达式来初始化,而全局变量只能用常量表达式(Constant Expression)初始化。例如,全局变量pi这样初始化是合法的:

double pi = 3.14 + 0.0016;

但这样初始化是不合法的:

double pi = acos(-1.0);

然而局部变量这样初始化却是可以的。程序开始运行时要用适当的值来初始化全局变量,所以初始值必须保存在编译生成的可执行文件中,因此初始值在编译时就要计算出来,然而上面第二种Initializer的值必须在程序运行时调用acos函数才能得到,所以不能用来初始化全局变量。请注意区分编译时和运行时这两个概念。为了简化编译器的实现,C语言从语法上规定全局变量只能用常量表达式来初始化,因此下面这种全局变量初始化是不合法的:

int minute = 360;
int hour = minute / 60;

虽然在编译时计算出hour的初始值是可能的,但是minute / 60不是常量表达式,不符合语法规定,所以编译器不必想办法去算这个初始值。

如果全局变量在定义时不初始化则初始值是0,如果局部变量在定义时不初始化则初始值是不确定的。所以,局部变量在使用之前一定要先赋值,如果基于一个不确定的值做后续计算肯定会引入Bug。

如何证明“局部变量的存储空间在每次函数调用时分配,在函数返回时释放”?当我们想要确认某些语法规则时,可以查教材,也可以查C99,但最快捷的办法就是编个小程序验证一下:

例 3.7. 验证局部变量存储空间的分配和释放

#include 

void foo(void)
{
int i;
printf("%d\n", i);
i = 777;
}

int main(void)
{
foo();
foo();
return 0;
}

第一次调用foo函数,分配变量i的存储空间,然后打印i的值,由于i未初始化,打印的应该是一个不确定的值,然后把i赋值为777,函数返回,释放i的存储空间。第二次调用foo函数,分配变量i的存储空间,然后打印i的值,由于i未初始化,如果打印的又是一个不确定的值,就证明了“局部变量的存储空间在每次函数调用时分配,在函数返回时释放”。分析完了,我们运行程序看看是不是像我们分析的这样:

134518128
777

结果出乎意料,第二次调用打印的i值正是第一次调用末尾赋给i的值777。有一种初学者是这样,原本就没有把这条语法规则记牢,或者对自己的记忆力没信心,看到这个结果就会想:哦那肯定是我记错了,改过来记吧,应该是“函数中的局部变量具有一直存在的固定的存储空间,每次函数调用时使用它,返回时也不释放,再次调用函数时它应该还能保持上次的值”。还有一种初学者是怀疑论者或不可知论者,看到这个结果就会想:教材上明明说“局部变量的存储空间在每次函数调用时分配,在函数返回时释放”, 那一定是教材写错了,教材也是人写的,是人写的就难免出错,哦,连C99也这么写的啊,C99也是人写的,也难免出错,或者C99也许没错,但是反正运行 结果就是错了,计算机这东西真靠不住,太容易受电磁干扰和宇宙射线影响了,我的程序写得再正确也有可能被干扰得不能正确运行。

这是初学者最常见的两种心态。不从客观事实和逻辑推理出发分析问题的真正原因,而仅凭主观臆断胡乱给问题定性,“说你有罪你就有罪”。先不要胡乱怀疑,我们再做一次实验,在两次foo函数调用之间插一个别的函数调用,结果就大不相同了:

int main(void)
{
foo();
printf("hello\n");
foo();
return 0;
}

结果是:

134518200
hello
0

这一回,第二次调用foo打印的i值又不是777了而是0,“局部变量的存储空间在每次函数调用时分配,在函数返回时释放”这个结论似乎对了,但另一个结论又不对了:全局变量不初始化才是0啊,不是说“局部变量不初始化则初值不确定”吗?

关键的一点是,我说“初值不确定”,有没有说这个不确定值不能是0?有没有说这个不确定值不能是上次调用赋的值?在这里“不确定”的准确含义是:每次调用这个函数时局部变量的初值可能不一样,运行环境不同,函数的调用次序不同,都会影响到局部变量的初值。在运用逻辑推理时一定要注意,不要把必要条件(Necessary Condition)当充分条件(Sufficient Condition),这一点在Debug时尤其重要,看到错误现象不要轻易断定原因是什么,一定要考虑再三,找出它的真正原因。例如,不要看到第二次调用打印777就下结论“函数中的局部变量具有一直存在的固定的存储空间,每次函数调用时使用它,返回时也不释放,再次调用函数时它应该还能保持上次的值”,这个结论倒是能推出777这个结果,但反过来由777这个结果却不能推出这样的结论。所以说777这个结果是该结论的必要条件,但不是充分条件。也不要看到第二次调用打印0就断定“局部变量未初始化则初值为0”,0这个结果是该结论的必要条件,但也不是充分条件。至于为什么会有这些现象,为什么这个不确定的值刚好是777,或者刚好是0,等学到就能解释这些现象了。

从介绍的语法规则可以看出,非定义的函数声明也可以写在局部作用域中,例如:

int main(void)
{
void print_time(int, int);
print_time(23, 59);
return 0;
}

这样声明的标识符print_time具有局部作用域,只在main函数中是有效的函数名,出了main函数就不存在print_time这个标识符了。

写非定义的函数声明时参数可以只写类型而不起名,例如上面代码中的void print_time(int, int);,只要告诉编译器参数类型是什么,编译器就能为print_time(23, 59)函数调用生成正确的指令。另外注意,虽然在一个函数体中可以声明另一个函数,但不能定义另一个函数,C语言不允许嵌套定义函数[]


阅读(2052) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~