• 为什么要少用全局变量


         为什么要少用全局变量?甚至有些公司禁止用全局变量。有一个说法是这样的,全局变量的最佳前缀是什么?答://

     接下来就粗略说说这个问题。

    1、全局变量和局部变量

    (1)全局变量:定义在函数外,存放空间为静态存储区,作用域为整个工程文件,若其它文件使用该变量,可以在本文件中用extern声明一遍该变量或者包含声明了该变量的头文件;在整个程序运行期间全局变量的值都会存在。由于同工程中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值, 就能影响到其他函数中全局变量的值。

    (2)静态全局变量:只在定义它的文件内有效,效果和全局变量一样,不过就在本文件内部;也就是说,用static修饰过的全局变量,改变了他的作用域,限制了他的使用范围。

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

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

    关于四者的性能如表1-1所示。

                        表1-1:

    内存空间作用域生存期
    全局变量静态存储区全局整个程序运行期间
    静态全局变量静态存储区作用于定义它的文件里整个程序运行期间
    局部变量作用于定义它的函数里定义的函数运行期间
    静态局部变量静态存储区作用于定义它的函数里整个程序运行期间

    2、全局变量的优点和缺点

    优点:

    (1)全局可见,任何 一个函数或线程都可以读写全局变量-同步操作简单。

     (2)内存地址固定,读写效率比较高。

    缺点

    (1)过多的全局变量会占用较多的内存单元:全局变量保存在静态存贮区,程序开始运行时为其分配内存,程序结束释放该内存。与局部变量的动态分配、动态释放相比,生存期比较长,因此过多的全局变量会占用较多的内存单元。
    (2)全局变量破坏了函数的封装性能:前面的章节曾经讲过,函数象一个黑匣子,一般是通过函数参数和返回值进行输入输出,函数内部实现相对独立。但函数中如果使用了全局变量,那么函数体内的语句就可以绕过函数参数和返回值进行存取,这种情况破坏了函数的独立性,使函数对全局变量产生依赖。同时,也降低了该函数的可移植性。
    (3)全局变量使函数的代码可读性降低:由于多个函数都可能使用全局变量,函数执行时全局变量的值可能随时发生变化,对于程序的查错和调试都非常不利。

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

    (5)增加了bug出现的概率:全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的。

    (6)全局变量的读写,可能会延迟,这主要是体现在“写”操作上,由于写操作,一般需要2个周期操作,所以有可能会出现,这边没写完时,那边已经读了,结果读到的不是最终值,这个是一个概率事件,概率 很小,但是并不代表没有。

    (7)在程序运行时,根据需要到内存中相应的存储单元中调用,如果一个变量在程序中频繁使用,例如循环变量,那么,系统就必须多次访问内存中的该单元,影响程序的执行效率。因此,C\C++语言还定义了一种变量,不是保存在内存上,而是直接存储在CPU中的寄存器中,这种变量称为寄存器变量。变量存储在寄存器上,就相当于直接操作CPU,程序当然会运转的很流畅;计算速度也是很快。全局变量不是分配在寄存器,对于一些循环,一定要避免频繁使用全局变量。但是如果又避免不了使用全局变量,一个巧妙的方法就是把全局变量赋值给一个临时变量,对临时变量进行操作,最后再将临时变量的值赋给全局变量。

    3、减少使用全局变量的方法

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


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

    (a)如果只有某.c文件用,就用static限制作用域,如表1-1所示,静态全局变量的作用域是定义它的文件,工程中的其它文件不能使用。
    (b) 如果只有一个函数用,那就定义局部变量。
    (c) 如果非要开放出去让人读取,那就用函数return出去,这样该变量只读属性;防止被其它文件中的函数改写。
    (d)如果其它文件一定要修改这个变量,就编写get_xxx和set_xxx接口函数。

    例如:

    timer.c

    1. static volaitle uint32_t s_wCounter;
    2. ISR(Timer0_vect)
    3. {
    4.     s_wCounter++;
    5. }
    6. uint32_t get_counter(void)
    7. {
    8.     uint32_t wResult;
    9.     SAFE_ATOM_CODE(                           //! 原子操作的保护宏
    10.         wResult = s_wCounter;
    11.     )
    12.     return wResult;
    13. }
    14. void set_count(uint32_t  count)
    15. {
    16.   s_wCounter = count;
    17. }

    timer.h

    1. #ifndef __TIMER_H__
    2. #define __TIMER_H__
    3. extern uint32_t get_counter(void);
    4. extern void set_count(uint32_t  count);
    5. #endif

    在其它文件中就可以通过get_counter() 和set_count() 两个接口函数来操作静态全局变量s_wCounter。

    (e)如果某个模块数据比较多,如果每个变量都编写接口函数显然太麻烦,此时可以通过结构体,联合体,位域将众多的参数进行封装,然后再通过接口函数进行操作。

    下面是采用类似于stm32库函数的做法, 对这些参数进行打包封装为“结构体变量”类型, 并在h文件中导出。在c文件中把这个参数定义为static类型(静态变量),并实现读/写接口函数。

     // 模块接口h文件中:
    typedef struct {
      Param_A;
      Param_B;
      Param_C;
    } User_Param_Typedef;

    bool ModuleName_SetParam(User_Param_Typedef *param);
    bool ModuleName_GetParam(User_Param_Typedef *param);

     // 模块实现c文件中:
    static User_Param_Typedef user_param;
    static bool user_access_locked;

    bool ModuleName_SetParam(User_Param_Typedef *param)
    {
      // 此处还可以增加对参数的保护特性
      user_param.Param_A = param->Parma_A;
      user_param.Param_B = param->Parma_B;
      user_param.Param_C = param->Parma_C;
      return true;
    }

    bool ModuleName_GetParam(User_Param_Typedef *param)
    {
      // 此处还可以增加对参数的保护特性
      param->Param_A = user_param.Parma_A;
      param->Param_B = user_param.Parma_B;
      param->Param_C = user_param.Parma_C;
      return true;
    }
     

     // app文件中:

    ...
    // 配置参数
    User_Param_Typedef  user_param;    // 临时变量(局部变量 ) 或 静态变量
    user_param.Param_A = 1;
    user_param.Param_B = 2;
    user_param.Param_C = 3;
    ModuleName_SetParam(&user_param);
    ...

    // 获取参数
    ModuleName_GetParam(&user_param);

    如果在应用中, 仅需要修改其中一个参数,可以根据上面的类似的方法修改, 增加基于 “基地址偏移量” 读/写的接口函数。

    参考文件:

    C语言中,全局变量滥用的后果竟如此严重?_张巧龙的博客-CSDN博客

    c语言中如何避免过多使用全局变量_李肖遥的博客-CSDN博客

    如何尽量地避免使用全局变量呢? (amobbs.com 阿莫电子论坛 - 东莞阿莫电子网站)

    全局变量的优缺点及为什么要少用全局变量_猪哥-嵌入式的博客-CSDN博客_全局变量的好处

  • 相关阅读:
    从源码方面来分析Fragment管理中 Add() 方法
    Vue中父子组件通信方式
    8.8 opencv
    三步实现Mybatis(Mybatis-Plus)多数据源配置
    基于ssm+mysql+jsp学生成绩管理系统(含实训报告)
    初学Linux(学习笔记)
    java学习之git的基本使用
    团建游戏------特尔斐决策技术
    vue3 搭配ElementPlus做基础表单校验 自定义表单校验
    SD-WAN让跨境网络访问更快、更安全!
  • 原文地址:https://blog.csdn.net/liuyi1591537136/article/details/127917182