:::info

https://mp.weixin.qq.com/s?__biz=MzI0ODU0NDI1Mg==&mid=2247493125&idx=1&sn=e99446e1e67351e628f2b78b1fc7c66b&chksm=e99d8896deea0180d1147ad57a76084a16ad924968ddf9cfc9374c30c18b40701ba61485d899&scene=132#wechat_redirect

:::

01啥是全局变量

说起全局变量,就不得不提到 “全局变量,局部变量,静态全局变量,静态局部变量”,这些都是编程语言中的基本概念。变量分为局部与全局,局部变量又可称之为内部变量。由某对象或某个函数所创建的变量通常都是局部变量,只能被内部引用,而无法被其它对象或函数引用。

全局变量既可以是某对象函数创建,也可以是在本程序任何地方创建。全局变量是可以被本程序所有对象或函数引用。

从作用域看:

全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包括全局变量定义的源文件需要用 extern 关键字再次声明这个全局变量。

静态局部变量具有局部作用域。它只被初始化一次,自从第一次初始化直到程序与你新内阁结束都一直存在,他和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。

局部变量也只有局部作用域,他是自动对象,他在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用结束后,变量就被撤销,其所占用的内存也被收回。

静态全局变量也具有全局作用域,他与全局变量的区别在于如果程序包含多个文件的话,他作用于定义它的文件里,不能作用到其他文件里,即被 static 关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同的静态全局变量,他们也是不同的变量。

从分配内存空间看:

全局变量、静态局部变量、静态全局变量都在静态存储区分配空间,而局部变量在栈分配空间。

全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上没有什么不同。区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。

1、静态变量会被放在程序的静态数据存储区里,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是他与堆栈变量和堆变量的区别

2、变量用 static 告知编译器,自己仅仅在变量的作用域范围内可见。这一点是他与全局变量的区别。

从以上分析可以看出,把局部变量改变为静态变量后是改变了他的存储方式,即改变了他的生存期。把全局变量改变为静态变量后是改变了他的作用域,限制了他的使用范围,因此 static 这个说明符在不同的地方起的作用是不同的。

简单来说就是:

全局变量: 在整个工程文件内都有效;“在函数外定义的变量”,即从定义变量的位置到本源文件结束都有效。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值, 就能影响到其他函数中全局变量的值。

静态全局变量: 只在定义它的文件内有效,效果和全局变量一样,不过就在本文件内部;

静态局部变量: 只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;静态局部变量的生存期虽然为整个工程,但是其作用仍与局部变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

局部变量: 在定义它的函数内有效,但是函数返回后失效。“在函数内定义的变量”,即在一个函数内部定义的变量,只在本函数范围内有效。

注意: 全局变量和静态变量如果没有手工初始化,则由编译器初始化为 0。局部变量的值不可知。

静态局部变量全局变量最明显的区别就在于:全局变量在其定义后所有函数都能用,但是静态局部变量只能在一个函数里面用。

通过一个代码来解释就是:

  1. #include <stdio.h>
  2. int num1 = 222;
  3. static int num2 = 111;
  4. int add(int a,int b)
  5. {
  6. static int tempSum = 0;
  7. tempSum = tempSum + a + b;
  8. return tempSum;
  9. }
  10. int main(void)
  11. {
  12. printf("num1=%d,num2=%d\n",num1,num2);
  13. int sum = 0;
  14. sum = add(num1,num2);
  15. printf("first time sum=%d\n",sum);
  16. sum = add(num1,num2);
  17. printf("second time sum=%d\n",sum);
  18. return 0;
  19. }

02新手最容易犯的问题

嵌入式特别是单片机 os-less 的程序,最易范的错误是全局变量满天飞。 此现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。

在. h 文档里面定义许多杂乱的结构体,extern 一堆令人头皮发麻的全局变量,然后再这个模块里边赋值 123,那个模块里边判断 123 分支决定做什么。

每当看到这种程序,我总要戚眉变脸而后拍桌怒喝。 没错,就是怒喝。我不否认全局变量的重要性,但我认为要十分谨慎地使用它,滥用全局变量会引申带来其它更为严重的结构性系统问题。

  1. 滥用全局变量会造成不必要的常量频繁使用,特别当这个常量没有用宏定义 “正名” 时,代码阅读起来将万分吃力。
  2. 会导致软件分层的不合理,全局变量相当于一条快捷通道 **,它容易使程序员模糊了 “设备层” 和“应用层”之间的边界。写出来的底层程序容易自作多情地关注起上层的应用。这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往 bug 一堆 **,处处 “补丁”,雷区遍布。说是度日如年举步维艰也不为过。
  3. 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块,而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个 “泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。
  4. 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的 bug 就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。

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

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

1.“老人” 气昂昂,因为系统离不开他,所有 “雷区” 只有他了然于心。当出现紧急的 bug 时,只有他能够搞定。你不但不能辞退他,还要给他加薪。

2. 新人见光死,但凡招聘来维护这个系统的,除了改出更多的 bug 外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。

  1. 随着产品的后续升级,几个月没有接触这个系统的原创者会发现,很多雷区他本人也忘记了,于是每次的产品升级维护周期越来越长,因为修改一个功能会冒出很多 bug,而按下一个 bug,会弹出其他更多的 bug。在这期间,又会产生更多的全局变量。终于有一天他告诉老板,不行啦不行啦,资源不够了,ram 或者 flash 空间太小了,升级升级。

4. 客户投诉不断,售后也快崩溃了,业务员也不敢推荐此产品了,市场份额越来越小,公司形象越来越糟糕。

03那么有什么对策?

1. 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。

2. 如果不可避免需要用到,那能藏多深就藏多深。

  1. 如果只有某. c 文件用,就 static 到该文件中,顺便把结构体定义也收进来;
  2. 如果只有一个函数用,那就 static 到函数里面去;
  3. 如果非要开放出去让人读取,那就用函数 return 出去,这样就是只读属性了;
  4. 如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值;
  5. 实在非要 extern 我,我还可以严格控制包含我. h 档的对象,而不是放到公共的 includes.h 中被人围观,丢人现眼。

如此,你可明白我对全局变量的感悟有多深刻。悲催的我,已经把当年那些 “老人” 交给我维护的那些案子加班全部重新翻写了。你能明白吗,不要让人背后唾弃你哦。

04大量使用局部变量也会容易造成栈溢出

1. 全局变量是不可避免要用到的,每一个设备底层几乎都需要它来记录当前状态,控制时序,起承转合。但是尽量不要用来传递参数,这个很忌讳的。

2. 尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围。这一点,C++ 的 private 属性就是这么干的。这对将来程序的调试也很有好处。C 语言之所以有 ++ 版本,很大原因就是为了控制它的灵活性,要说面向对象的思想,C 语言早已有之,亦可实现。

3. 当一个模块里面的全局变量超过 3 个 (含) 时,就用结构体包起来吧。 要归 0 便一起归 0,省得丢三落四的。

  1. 在函数里面开个静态的全局变量,全局数组,是不占用栈空间的。 只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。若是在 keil C51,因为是静态编译,栈爆掉了会报警,所以大可以尽情驰骋,注意交通规则就是了。

5. 单片机的 os-less 系统中,只有栈没有堆的用法,那些默认对堆分配空间的 “startup.s”,可以大胆的把堆空间干掉

  1. 程序模型?如何分析抽象出来呢,从哪个角度进行模型构建呢?很愿意聆听网友的意见。本人一直以来都是从两个角度分析系统,事件 — 状态机迁移图 和 数据流图,前者分析控制流向,完善 UI,后者可知晓系统数据的缘起缘灭。 这些理论,院校的《软件工程》教材都有,大家不妨借鉴下。只不过那些理论,终究是起源于大型系统软件管理的,牛刀杀鸡,还是要裁剪一下的。

05【最后再来皮一下,不要尝试】全局变量的最佳前缀

问:全局变量的最佳前缀是什么?

答:

//

↓↓↓

【C语言】全局变量 - 图1