我是老温,一名热爱学习的嵌入式工程师- @4 W0 w* N, s+ A6 T
关注我,一起变得更加优秀!/ x; X8 R1 M$ f0 ]9 z. ~
嵌入式特别是单片机os-less的程序,最易范的错误是全局变量满天飞。/ y' A K5 T e; x9 t+ f
这个现象在早期汇编转型过来的程序员以及初学者中常见,这帮家伙几乎把全局变量当作函数形参来用。
& `5 I2 P% r6 X2 B- L$ h在.h文档里面定义许多杂乱的结构体,extern一堆令人头皮发麻的全局变量,然后再这个模块里边赋值123,那个模块里边判断123分支决定做什么。
7 `; G' s4 T/ G. \每当看到这种程序,我总要戚眉变脸,然后各种问候。
$ [5 E/ x' Z( G- k$ i5 B, x; {我不否认全局变量的重要性,但我认为要十分谨慎地使用它,滥用全局变量会引申带来其它更为严重的结构性系统问题。' r2 R! ^- q2 p6 S) k X
3miyppfjyyq64047343805.jpg
9 C( U3 v# b* s8 X诸位看官,且听我细细道来0 A% a; t/ C o# J2 G- H8 E( {- I6 Y
1. 它会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。
! [7 F( }2 o( q4 j" L. g2. 它会导致软件分层的不合理,全局变量相当于一条快捷通道,它容易使程序员模糊了“设备层”和“应用层”之间的边界,写出来的底层程序容易自作多情地关注起上层的应用。
8 k$ E) q/ S8 v7 J7 M3 ~这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。说是度日如年举步维艰也不为过。
9 b% I8 S0 N0 x) h3. 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块。
" ~' H; |% K: a7 Q4 b而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。( E: Z% g8 P: c% h |- j
4. 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。% \; ~2 }; W0 ^8 |5 z/ c
无需多言,您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态!' V6 ^. A0 Y9 \: ^
你看着这台机器,机器也看着你,相对无言,心中发毛。你不确定它什么时候会崩溃,也不晓得下一次投诉什么时候道理。
" j% l, a; K$ l& A# C6 L
3voqnsirj3u64047343905.jpg
# i7 R5 K& |% w$ A2 J' u" c. b现实层面的后果是什么
7 c5 C4 K) {" q/ O8 `1.“老人”气昂昂,因为系统离不开他,所有“雷区”只有他了然于心。当出现紧急的bug时,只有他能够搞定。你不但不能辞退他,还要给他加薪。) B1 S2 Z8 r; c. S! t
2. 新人见光死,但凡招聘来维护这个系统的,除了改出更多的bug外,基本上一个月内就走人,到了外面还宣扬这个公司的软件质量有够差够烂。
0 U. I) U2 V; A( S3. 随着产品的后续升级,几个月没有接触这个系统的原创者会发现,很多雷区他本人也忘记了,于是每次的产品升级维护周期越来越长。
, x; X3 \* V2 a* O4 Q7 H9 K因为修改一个功能会冒出很多bug,而按下一个bug,会弹出其他更多的bug。在这期间,又会产生更多的全局变量。' f* k2 k1 A, @+ y5 k3 }) Q
终于有一天他告诉老板,不行啦不行啦,资源不够了,ram或者flash空间太小了,升级升级。8 t1 p0 M" B) {5 i8 [5 S
4. 客户投诉不断,售后也快崩溃了,业务员也不敢推荐此产品了,市场份额越来越小,公司形象越来越糟糕。/ Q; c& t0 [' g" ^
313h3wlsamj64047344005.jpg
1 P' e5 ]1 s7 P' M
要说对策,只有两个原则
" V1 S1 x4 U: k m( Y4 i5 V1. 能不用全局变量尽量不用,我想除了系统状态和控制参数、通信处理和一些需要效率的模块,其他的基本可以靠合理的软件分层和编程技巧来解决。, }9 o/ o& X# Q* I0 b! o/ u1 N, P
2. 如果不可避免需要用到,那能藏多深就藏多深。! x- S3 Z. B: N
1)如果只有某.c文件用,就static到该文件中,顺便把结构体定义也收进来;9 p9 |/ D2 ^) @5 ?5 b
2)如果只有一个函数用,那就static到函数里面去;, g6 p6 e5 b" w9 g& ?
3)如果非要开放出去让人读取,那就用函数return出去,这样就是只读属性了;9 B6 ~8 a+ Q" m2 H
4)如果非要遭人蹂躏赋值,好吧,我开放函数接口让你传参赋值;
$ m0 d& \8 _0 }8 Q- h0 q5)实在非要使用extern声明,我还可以严格控制包含我.h档的对象,而不是放到公共的 includes.h 中被人围观,丢人现眼。
* w! d8 V6 F+ v' f3 K如此,你可明白我对全局变量的感悟有多深刻。
( @7 g+ s0 H: E7 x) b; Y5 u {悲催的我,已经把当年那些“老人”交给我维护的那些案子加班全部重新翻写了。你能明白吗,不要让人背后唾弃你哦。
! i; y% r! [3 n: w; f' t1 {
dxpn4ov24xw64047344105.jpg
/ V: L- P/ y) r [6 w9 l9 B) k- D最后补充一下意见. f. t3 o T# [
1. 全局变量是不可避免要用到的,每一个设备底层几乎都需要它来记录当前状态,控制时序,起承转合。但是尽量不要用来传递参数,这个很忌讳的。, C) h7 ]% p$ g& E5 {! h
2. 尽量把变量的作用范围控制在使用它的模块里面,如果其他模块要访问,就开个读或写函数接口出来,严格控制访问范围,这一点,C++的private属性就是这么干的。
! O7 a2 S* B/ ^) w. O; u这对将来程序的调试也很有好处。C语言之所以有++版本,很大原因就是为了控制它的灵活性,要说面向对象的思想,C语言早已有之,亦可实现。3 N4 j2 e2 M8 H
3.当一个模块里面的全局变量超过3个(含)时,就用结构体包起来吧。要归0便一起归0,省得丢三落四的。
$ I( }8 W; P2 B) ^& l* }4.在函数里面开个静态的全局变量,全局数组,是不占用栈空间的,只是有些编译器对于大块的全局数组,会放到和一般变量不同的地址区。
7 L* U( g4 a+ J若是在keil C51,因为是静态编译,栈爆掉了会报警,所以大可以尽情驰骋,注意交通规则就是了。! X8 Q, h, j# x$ W: g
5.单片机的os-less系统中,只有栈没有堆的用法,那些默认对堆分配空间的“startup.s”,可以大胆的把堆空间干掉。
: }% F% Y7 j/ e7 }$ c6.程序模型?如何分析抽象出来呢,从哪个角度进行模型构建呢?很愿意聆听网友的意见。3 f1 f2 K- R; r7 M% z5 U$ w
本人一直以来都是从两个角度分析系统,事件--状态机迁移图 和 数据流图,前者分析控制流向,完善UI,后者可知晓系统数据的缘起缘灭。
' M! k- O6 c' `3 j7 X这些理论,院校的《软件工程》教材都有,大家不妨借鉴下。只不过那些理论,终究是起源于大型系统软件管理的,牛刀杀鸡,还是要裁剪一下的。
4 U4 y* p. N+ S0 [' G( a来源:牛逼的工程师网友。
& \) b# ]$ P8 V& J-END-
9 G9 J- e# B' P3 }( ]往期推荐:点击图片即可跳转阅读+ K( f) u+ i r; h; D
qmxbzmkduzr64047344205.jpg
8 o! q2 `9 \9 e) G嵌入式软件工程师,除了coding之外,还需要锻炼哪些技能?8 ]# T3 S; ^+ f
kpp0gqimkxl64047344305.jpg
3 N2 K) M4 J, l* T4 a$ Z! f如何应对枯燥的嵌入式软件开发工作?9 I9 u4 K' H. D3 X C9 h
cod2tuhkmyg64047344405.jpg
9 e9 r, C. ]" `/ b( Y用 C 语言编写的嵌入式软件,需要强调高内聚低耦合吗?
/ {, f( E7 j2 m: J我是老温,一名热爱学习的嵌入式工程师
b/ U! X' ^% W关注我,一起变得更加优秀! |