使用全局变量的危害

 嵌入式特别是单片机os-less的程序,最易范的错误是全局变量满天飞。这个现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。在.h文档里面定义许多杂乱的结构体,extern一堆令人头皮发麻的全局变量,然后再这个模块里边赋值123,那个模块里边判断123分支决定做什么。每当看到这种程序,我总要戚眉变脸而后拍桌怒喝。没错,就是怒喝。我不否认全局变量的重要性,但我认为要十分谨慎地使用它,滥用全局变量会引申带来其它更为严重的结构性系统问题。

诸位看官,且听我细细道来。

1. 它会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。

2. 它会导致软件分层的不合理,全局变量相当于一条快捷通道,它容易使程序员模糊了“设备层”和“应用层”之间的边界。写出来的底层程序容易自作多情地关注起上层的应用。这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。说是度日如年举步维艰也不为过。

3. 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块,而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。

4. 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。

无需多言,您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态!你看着这台机器,机器也看着你,相对无言,心中发毛。你不确定它什么时候会崩溃,也不晓得下一次投诉什么时候到来。

然后,我告诉大家现实层面的后果是什么。

1.“老人”气昂昂,因为系统离不开他,所有“雷区”只有他了然于心。当出现紧急的bug时,只有他能够搞定。你不但不能辞退他,还要给他加薪。
2. 新人见光死,但凡招聘来维护这个系统的,除了改出更多的bug外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。
3.随着产品的后续升级,几个月没有接触这个系统的原创者会发现,很多雷区他本人也忘记了,于是每次的产品升级维护周期越来越长,因为修改一个功能会冒出很多bug,而按下一个bug,会弹出其他更多的bug。在这期间,又会产生更多的全局变量。终于有一天他告诉老板,不行啦不行啦,资源不够了,ram或者flash空间太小了,升级升级。
4. 客户投诉不断,售后也快崩溃了,业务员也不敢推荐此产品了,市场份额越来越小,公司形象越来越糟糕。

要问我的对策吗,只有两个原则:
1. 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。
2. 如果不可避免需要用到,那能藏多深就藏多深。
1)如果只有某.c文件用,就static到该文件中,顺便把结构体定义也收进来;
2)如果只有一个函数用,那就static到函数里面去;
3)如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了;
4)如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值;5)实在非要extern**我,我还可以严格控制包含我.h档的对象,而不是放到公共的includes.h中被人围观,丢人现眼。
如此,你可明白我对全局变量的感悟有多深刻。悲催的我,已经把当年那些“老人”交给我维护的那些案子加班全部重新翻写了。你能明白吗,不要让人背后唾弃你哦。

续篇
  对于一些网友提到的,如果大量使用局部变量也会容易造成栈溢出的问题,还提到程序模型的概念。言之有理。所以特地来补充一下意见:
1.全局变量是不可避免要用到的,每一个设备底层几乎都需要它来记录当前状态,控制时序,起承转合但是尽量不要用来传递参数,这个很忌讳的。
2.尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围。这一点,C++的private属性就是这么干的。这对将来程序的调试也很有好处。C语言之所以有++版本,很大原因就是为了控制它的灵活性,要说面向对象的思想,C语言早已有之,亦可实现。
3.当一个模块里面的全局变量超过3个(含)时,就用结构体包起来吧。要归0便一起归0,省得丢三落四的。
4.在函数里面开个静态的全局变量,全局数组,是不占用栈空间的只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。若是在keil C51,因为是静态编译,栈爆掉了会报警,所以大可以尽情驰骋,注意交通规则就是了。
5.单片机的os-less系统中,只有栈没有堆的用法,那些默认对堆分配空间的“startup.s”,可以大胆的把堆空间干掉。
6.程序模型?如何分析抽象出来呢,从哪个角度进行模型构建呢?很愿意聆听网友的意见。本人一直以来都是从两个角度分析系统,事件--状态机迁移图 和 数据流图,前者分析控制流向,完善UI,后者可知晓系统数据的缘起缘灭。这些理论,院校的《软件工程》教材都有,大家不妨借鉴下。只不过那些理论,终究是起源于大型系统软件管理的,牛刀杀鸡,还是要裁剪一下的。

<think>我们正在讨论C语言中全局变量的定义位置问题。用户询问:全局变量能否定义在函数声明之后?根据C语言标准,全局变量(即具有文件作用域的变量)的定义位置是灵活的,可以出现在函数声明之前或之后。但是,变量的作用域是从其定义点开始到文件结束。这意味着如果变量定义在函数声明之后,那么在该函数声明之前(即函数定义中)无法直接使用这个全局变量,因为此时变量尚未声明。 引用分析: 引用[1]讨论了局部变量和全局变量的存储类别,指出局部变量默认存储在动态存储区(除非声明为static)[^1]。 引用[2]提到static关键字在函数和变量声明中的作用,特别是在数据声明中表示该定义不导出到连接程序(即内部链接)[^2]。 引用[3]强调static全局变量应放在源文件中而非头文件,以避免多个编译单元拥有独立的拷贝[^3]。 引用[4]说明了多文件中全局变量的重定义规则(强符号和弱符号)[^4]。 用户的问题并不直接涉及static,而是关于全局变量(这里指普通的非static全局变量,即外部链接的全局变量)的定义位置。但注意,如果全局变量用static修饰,则其链接属性为内部,不影响这里的讨论(位置规则相同)。 回答要点: 1. 全局变量(包括static和非static)可以定义在函数声明之后。 2. 但是,在定义点之前的函数无法直接访问该全局变量(因为编译器尚未知道该变量存在)。 3. 如果需要在定义点之前使用,可以在使用之前添加该变量的声明(使用extern关键字,但注意定义只能有一次)。 示例: 情况1:全局变量定义在函数声明之后,且函数在变量定义之前使用该变量(会导致编译错误): ```c #include <stdio.h> void foo(void) { // 这里使用全局变量global_var printf("%d\n", global_var); // 错误:编译时未知的标识符 } int global_var = 10; // 定义在函数foo之后 int main() { foo(); return 0; } ``` 情况2:在函数之前声明(不是定义)全局变量,然后在之后定义: ```c #include <stdio.h> extern int global_var; // 声明全局变量,告诉编译器存在这样一个变量,定义在其他地方(本文件后面) void foo(void) { printf("%d\n", global_var); // 正确:此时编译器知道global_var的存在 } int global_var = 10; // 定义全局变量 int main() { foo(); return 0; } ``` 注意:在同一个文件中,我们通常使用extern声明来提前引用全局变量。但是,如果全局变量定义在另一个源文件中,则需要在当前文件使用extern声明(通常放在头文件中)。 另外,对于static全局变量,由于它具有内部链接,所以只能在当前文件使用。同样,如果定义在函数之后,那么在函数之前使用也需要提前声明(使用extern?),但是注意:static全局变量在声明时不能使用extern(否则会改变链接属性)。实际上,对于static变量,我们可以在使用之前再次使用static声明(但这不是定义,只是声明,而且必须与定义一致)。例如: ```c #include <stdio.h> static int global_var; // 声明且定义(未初始化,为0)?注意:这里实际上是定义,因为声明static变量时如果没有extern则视为定义。 void foo(void) { printf("%d\n", global_var); } int main() { foo(); return 0; } ``` 但是,如果我们想将static全局变量的定义放在后面,而前面使用,可以这样: ```c #include <stdio.h> static int global_var; // 声明(这里实际上是定义,因为static变量在文件作用域中,如果没有初始化,也是定义,且初始化为0) // 但如果我们想将定义放在后面,而前面只做声明,是不行的。因为static变量在文件作用域中,每一个声明都必须是定义(除非使用extern,但extern和static不能同时出现)? // 实际上,C语言中,对于static全局变量,我们只能定义一次。所以通常的做法是定义在使用之前。如果必须定义在后面,那么可以在前面使用一个声明(不带extern),但这样会导致两个定义?这是不允许的。 // 正确做法:将定义放在前面。如果一定要放在后面,那么可以在前面使用一个extern声明?但是C语言规定,static全局变量不能使用extern来声明(因为extern表示外部链接,而static是内部链接,冲突)。 // 所以结论:对于static全局变量,我们只能在使用之前定义。不能在定义之前使用,并且无法通过前置声明来提前使用(因为无法用extern声明,而单独的static声明会导致定义)。 // 因此,对于static全局变量,我们通常将其定义在文件的顶部(所有函数之前)以避免任何问题。 ``` 根据C标准,在文件作用域内,使用static的变量定义只能有一次。如果我们在前面写一个声明并带static,那么它就是一个定义(即使没有初始化,也是定义并初始化为0)。所以不能这样写: ```c static int global_var; // 定义(初始化为0) ... static int global_var = 10; // 错误:重复定义 ``` 因此,对于static全局变量,我们无法在使用之后定义,除非在同一个文件作用域内,我们只能定义一次,所以必须在使用之前定义。 总结: 1. 普通全局变量(非static)可以定义在函数声明之后,但要在使用之前用extern声明(可以在函数外部提前声明)。 2. static全局变量必须在使用之前定义,因为无法在定义之前声明(没有有效的前置声明方法)。 所以,回答用户的问题:全局变量(非static)可以定义在函数声明之后,但需要在使用之前进行声明(用extern);而static全局变量则必须在使用之前定义。 注意:用户问题中“函数声明”可能指函数定义(实现)或函数原型。我们这里假设函数声明包括函数原型和函数定义。在函数定义中使用全局变量,如果全局变量定义在函数定义之后,则需要在函数定义之前声明该变量(用extern)。 最后,建议将全局变量的定义放在文件顶部(函数定义之前),以避免需要额外的前置声明。 引用[3]中提到,全局变量(特别是static全局变量)应该放在源文件中而不是头文件,这是为了避免多个源文件包含同一个头文件时产生多个独立的static变量拷贝[^3]。这个建议与定义位置无关,而是关于在头文件中定义static变量的危害。 因此,回答用户问题:可以,但需要注意使用前置声明(对于非static全局变量)或提前定义(对于static全局变量)。 生成相关问题: 1. C语言中全局变量的前置声明应该如何正确使用? 2. static全局变量和普通全局变量在链接属性上有何区别? 3. 在头文件中定义全局变量会导致什么问题? 4. 如何避免多个源文件中全局变量的重定义错误? 5. 在C语言中,extern关键字的作用是什么?</think>### C语言全局变量定义位置详解 在C语言中,**全局变量可以定义在函数声明之后**,但需要注意作用域规则和访问顺序。以下是关键要点: #### 1. **全局变量的作用域规则** - 全局变量的作用域从**定义点开始**,直到文件结束 - 定义在函数声明之后的全局变量: - ✅ **可以被后续的函数访问** - ❌ **不能被前面的函数直接访问** ```c void func1() { // printf("%d", global_var); // 错误!此时global_var尚未定义 } int global_var = 42; // 全局变量定义在函数声明后 void func2() { printf("%d", global_var); // 正确!可以访问 } ``` #### 2. **前置声明解决方案** 若需要在函数声明前使用全局变量,需添加`extern`声明: ```c extern int global_var; // 前置声明(不分配内存) void func1() { printf("%d", global_var); // 现在可以访问 } int global_var = 42; // 实际定义(分配内存) ``` - `extern`声明仅表示"此变量在其他位置定义" - 实际定义只能出现一次(避免链接错误)[^4] #### 3. **static全局变量的特殊规则** ```c static int internal_var = 10; // static全局变量(内部链接) ``` - 必须**在访问前定义**(无前置声明机制) - 应定义在源文件顶部(而非头文件)[^3] - 每个编译单元有独立副本(避免头文件包含污染)[^3] #### 4. **最佳实践建议** 1. **普通全局变量**: - 优先定义在文件顶部 - 需要后置定义时使用`extern`前置声明 2. **static全局变量**: - 始终定义在文件顶部 - 禁止在头文件中定义[^3] 3. **多文件项目**: - 在`.c`文件中定义全局变量 - 在`.h`中用`extern`声明(非static变量) - 使用`#ifndef`头文件保护防止重复包含 > ⚠️ 错误示例:头文件中定义static全局变量 > ```c > // utils.h (错误做法!) > static int counter = 0; // 每个包含此头文件的源文件将获得独立副本 > ``` > 这会导致多个编译单元中的`counter`地址不同[^3],值无法同步。 #### 引用说明 - 引用[3]解释了static全局变量在头文件中的危害[^3] - 引用[4]说明了多文件全局变量重定义规则[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值