编码规则(可维护性)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第2章维护性
2006/11/10 V1.00 大多嵌入式软件开发中,都会在制作完成的软件上进行维护作业。
维护的原因各种各样,例如:
・发布的软件中发现Bug,需要修改。
・对应产品的市场要求,以既存软件为基础,追加新的功能。
等等。
像这样,在制作好的软件上加工,要尽量避免错误,有效的进行。
系统界管这叫维护性。
在此,整理了维护、提高嵌入式软件源代码维护性的一些惯用方法。
①维护性1・・・意识到其他人也会看你的代码。
②维护性2・・・使用不会改错得方法。
③维护性3・・・把程序尽量简单化。
④维护性4・・・统一编码方法。
⑤维护性5・・・使用便于测试的编码方法。
⑥维护性6・・・Uniden(株)Know-how集。
维护性1
意识到其他人也会看你的代码。
在制作源代码时考虑到,它会被制作者以外的技术者再利用或维护。
因此,源代码要使用容易理解的表现方式。
「维护性1 」有以下12个惯用做法。
维护性1.1不保留不使用的代码。
维护性1.2不使用麻烦,杂乱的写法。
维护性1.3不使用特殊的写法。
维护性1.4演算的优先顺序明确易懂。
维护性1.5不省略取得函数地址的演算、比较演算。
维护性1.6一个领域用于一个目的。
维护性1.7不重复使用名字。
维护性1.8不使用容易理解错的语言规格。
维护性1.9在特殊的方法中写明意图。
维护性1.10 不掩埋Magic Number。
维护性1.11 明示领域属性。
维护性1.12 不编译的语句也要正确记述。
维护性1.1不遗留不使用的代码。
M1.1.1 不声明(定义)没有使用的函数、变量、参数、标签。
参考规则无
相关规则M1.9.1 M4.7.2
M1.1.2 不应该把代码的一部分“Comment out”。
参考规则MISRA-C 2.4
相关规则M1.2.1 M4.7.2
<正确例>
#if 0 /* 因为~、无効化*/
a++;
#endif
<不正确例>
・・・・・・・
/* a++; */
・・・・・・・
}
如果需要把代码部分无効化,建议不要用Comment out,而是用#if 0圈住。
或者确定一个无効化代码的明确规则也行。
但是,留下无効代码会导致代码不好读,还是应该避免的。
note
在调试时,会Comment out一部分代码,但是调试结束后不要忘了解除Comment ,否则可能会发生Bug。
如果限制了Comment out代码,就可以在早期发现这些Bug。
维护性1.2不使用麻烦,杂乱的写法。
M1.2.1 用于相同目的的相同类型的自动变量,可以用1个声明语句进行多次声明,但是不可以混合初始化的变量和不初始化的变量。
参考规则无
相关规则M1.6.1
<正确例>
int j, k;
int l = 0;
int *p;
int q;
<不正确例>
int j, k, l = 0; /* 混有初始化的内容*/
int *p, q; /* 混有不同类型的变量*/
如果声明了int *p;,那么类型就是int *、如果声明了int *p, q;,q的类型不是int *、而是被解释为int。
note
写常量时,如果不使用接尾语的话,整数常量为int型、浮动小数点常量为double型。
但是,整常量中如果有int不能表现的值,那么这个值将会变成能表现它的类型。
因此,0x8000在int为16bit时为
unsigned int,但在int为32bit时变成signed int。
如果想把它作为unsigned使用的话,把”U”写为接尾语。
另外,在浮动小数点的float型和double型演算速度不同的Target System中,进行float型的变量和浮动小数点的演算时,如果浮动小数点常量中没有”F”,这个演算就会变成”double”型的演算,这一点需要注意。
要在浮动小数点常量上多下些功夫,使其一看就能知道是浮动小数点常量,比如在小数点左右最少要写一个数字等等。
特别是在演算精度、演算速度很重要的程序中,必须要充分理解实际是使用的那种类型的演算。
(不要Cut&Try。
)
整数常量的类型
接尾语u/U: unsigned int, unsigned long
接尾语l/L: long, unsigned long
接尾语u/Uとl/Lの両方: unsigned long
浮动少数点常量的类型
接尾语f/F: float
接尾语l/L: long double
维护性1.3不使用特殊的方法。
M1.3.1 switch(式)的表达式中,不用求真假结果的表达式。
参考规则无
相关规则无
<正确例>
if (val1 == 0) {
val2 = 0;
} else {
val2 = 1
}
<不正确例>
switch (val1 == 0) {
case 0:
val2 = 1;
break;
default:
val2 = 0;
break;
}
如果在Switch语句中使用求真假结果的表达式,分歧数就会变成2个、不需要在多分歧命令的switch语句中使用。
switch语句和if语句相比,容易出现default节的错误记载、break语句的遗漏等错误,所以如果不是3分歧以上,建议使用if语句。
M1.3.3 switch(式)的表达式中,不用求真假结果的表达式。
参考规则MISRA-C 8.2 Indian Hill 8
相关规则M4.5.1
<正确例>
extern int global;
int func(void) {
・・・・・・・
}
<不正确例>
extern global;
func(void) {
・・・・・・・
}
在函数、变量的定义、声明中没有数据类型时,被解释为int型,明确记述数据型会看起来更方便。
维护性1.4演算的优先顺序明确易懂
M1.4.1 &&、||演算的右式和左式是单纯的变量或()括起来的公式。
但是,&&演算连续结合时,或||演算连续结合时、不需要用()括住&&式或||式。
参考规则无
相关规则R2.3.2 M1.5.2
<正确例>
if ((x > 0) && (x < 10))
if ((!x) || y)
if ((flag_tb[i]) && status)
if ((x != 1) && (x != 4) && (x != 10))
<不正确例>
if (x > 0 && x < 10)
if (!x || y)
if (flag_tb[i] && status)
if (x != 1 && x != 4 && x != 10)
只有单纯变量的表达式和用()括住的表达式叫做一次式。
一次式中,包括常量或文字列参数。
&&或||的各项有一次式的规则。
在有Operator的表达式中用()括住的目的是,&&或||演算的各项演算会变得很醒目,可以提高可读性。
note
在软件制造工程中,有各种技能的工程师进行作业。
特别是技能不好的工程师,可能对C语言规则的理解度不是很够。
所以,下功夫制作简单易懂的代码,让这些工程师也能正确理解代码是很重要的。
C语言中,Operator的优先顺序如下。
这个标的纵轴表示优先顺序的等级(数字越大,优先度越高)。
结合规则表示的是,优先顺序同等级时的演算顺序。
M1.4.2 为了明确演算的优先顺序,规定使用括号。
参考规则无
相关规则M1.5.1 制作规则。
<正确例>
a = (
b << 1) + c;
或
a =
b << (1 + c);
<不正确例>
a =
b << 1 + c; /*优先顺序有可能错误*/
C语言的Operator优先顺序因为很容易被看错,制定如下的规则比较好。
表达式中包括优先顺序不同的多个2项Operator时,为了明示优先顺序,使用括号。
但是,四则演算相关的可以省略括号。
note
Operator作用的变量叫做Operand。
取1个Operand的Operator叫「单项Operator」(Unary Operator),取2个Operand的Operator叫「二项Operator」(Binary Operator),取3个Operand的Operator叫「三项Operator」。
SizeofOperator为单项Operator,单纯代入Operator为二项Operator,?Operator为3项Operator。
单项Operator :反转Operator !, 间接指定(指针) *, 地址Operator &,Cast Operator (type), sizeof Operator, Increment++ Decrement , 理论(Loop)Operator && ||等二项Operator :算术Operator * / % + -, Bit Operator << >> & | ^ ~, 代入Operator =,+=,-=,*=,/=, 关系(比较)Operator <=, <, >=, >, ==, != 等
三项Operator :,`式1 ? 式2 : 式3;'
维护性1.5不省略取得函数地址的演算、比较演算。
M1.5.1 必须指定、使用函数标识符(函数名)前面是用&、还是括号的形式参数List(空的也行)。
参考规则MISRA-C 16.9
相关规则无
<正确例>
void func(void);
void (*fp)(void) = &func;
if (func()) {
<不正确例>
void func(void);
void (*fp)(void) = func; /* NG: 没有& */
if (func) { /* NG* 不是函数调用,而是取得地址。
有时可能会写成调用没有参数的
函数*/
C语言中、如果只取得函数名就不是函数调用,而是取得函数地址。
也就是说,取得函数地址不需要加& 。
但是,如果没有&,有时会弄错函数调用(Ada等在没有参数的子程序调用中,只写了名字时等)。
在取函数地址时,通过遵守“加&”的规则,可以检查出没有加&、也没有()的函数名、就能找到错误(失误)。
note
函数型的语法如下。
除sizeofOperator和地址Operator以外的表达式中,返回T型的函数(function returning type T),会变成指向返回T型函数的指针。
这个语法和队列一样,式特别准备的。
(不使用地址Operator也能取得地址的只有队列和函数。
)
例如,
有
double f(char, int);
时,函数指示子(式)f 变成函数f的地址。
可以接受函数地址的指针是「指向函数的指针」。
例如,在接收上述函数 f 的地址时,要变成声明了double (*pf)();
的指针pf 。
这就是指向返回double的函数的指针。
维护性1.61个领域只用于一个目的
M1.6.1 每个目的都准备一个变量。
参考规则MISRA-C 18.3
相关规则M1.2.1
<正确例>
/* 用于跟Counter变量替换的作业变量为其他变量*/
for (1 = 0; j < MAX; j++) {
data[j] = j ;
}
if (min > max) {
wk = max;
max = min;
min = wk;
}
<不正确例>
/*用于跟Counter变量替换的作业变量为相同变量*/
for (1 = 0; j < MAX; j++) {
data[j] = j ;
}
if (min > max) {
j = max;
max = min;
min = j;
}
再次利用变量会降低可读性,在修改时会增加修改错误的危险性。
note
为了减少堆帐使用量,有时会再次使用自由变量。
最近的编译器中就算准备各自的自由变量也不能最适化,所以没多大意义。
另外,再次使用自由变量会导致变量的存在期间长,所以堆帐使用量也可能会増加。
M1.6.2 使用共用体时,書き込んだMemberで参照する。
参考规则无
相关规则M1.2.3
<正确例>
/* typeがINT→i_var 、CHAR→c_var[4] */
struct stag {
int_type;
union utag {
char c_var[4];
int i_var;
} u_var;
} s_var;
・・・・・・・
int i;
・・・・・・・
if (s_var.type == INT) {
s_var.u_var.i_var = 1;
}
・・・・・・・
i = s_var.u_var.i_var;
<不正确例>
/* typeがINT→i_var 、CHAR→c_var[4] */
struct stag {
int_type;
union utag {
char c_var[4];
int i_var;
} u_var;
} s_var;
・・・・・・・
int i;
・・・・・・・
if (s_var.type == INT) {
s_var.u_var.c_var[0] = 0;
s_var.u_var.c_var[1] = 0;
s_var.u_var.c_var[2] = 0;
s_var.u_var.c_var[3] = 1;
}
・・・・・・・
i = s_var.u_var.i_var;
共用体可以在不同大小的领域中声明一个领域,但Member间的Bit 的重叠方式依赖于处理系统,所以不一定会变成想要的动作。
使用时需要注意。
note
MISRA-C 18.4中禁止使用共用体,因为依赖于处理系统的处理很多,所以尽量不用的好。
维护性1.7不再次使用同一个名字。
M1.7.1 名字的统一性遵从以下规则。
1 .外部Scope的标识符是隐蔽的,所以内部Scope的标识符中,不可以使用跟
外部Scope 一样的名字。
【MISRA 5.2】
2.typedef名必须是固有的标识符。
【MISRA 5.3】
3. Tab名必须是固有的标识符。
【MISRA 5.4】
4.持有静的记忆域期间的Object或函数标识符不应该再次使用。
【MISRA
5.5】
5. 除了构造体和共用体的Member名,不可以把一个Name Space的标识符用于
其他的Name Space标识符处。
【MISRA 5.6】
参考规则MISRA-C 5.2 5.3 5.4 5.5 5.6
相关规则M4.3.1
<正确例>
int var1;
void func(int arg1) {
int var2;
var2 = arg1;
{
int var3;
var3 = var2;
}
}
<不正确例>
int var1;
void func(int arg1) {
int var1; /* 名字和函数外侧的变量名一样*/
var1 = arg1;
{
int var1; /* 名字和外侧的有効范围中的变量名一样*/
…
var1 = 0; /* 不知道想代入哪个var1 */
除了自动变量等有効范围被限制时,名字在程序中要尽量保持统一,让程序易读。
C语言中,名字除了file、Block等限制的有効范围,还有另外的4个名字空间。
1.标签
2.Tab
3.构造体・共用体的Member
4.其他标识符
※宏没有名字空间
除了自动变量等有効范围被限制时,名字在程序中要尽量保持统一,让程序易读。
在语言规格上,如果名字空间不同,那么用相同的名字也行。
但这个规则中是限制的,目的是为了制作易读易懂的程序。
note
用于变量、函数命名的规则说明。
1.变量、函数的名字中能使用的文字有英文(大・小)、数字、Under Score(_)等共计63个文字。
这些文字可以自由组合形成名字。
但是,最初的文字一定要是英文或Under Score。
2.名字取多长都行,但通常有効的文字数只到第8个文字。
另外,名字不可以只是一个Under Score。
3.C语言中,明确区分了英语大写和小写。
通常,C语言中是用小写来写程序。
大写用于表示特殊意义。
4.C语言中,和其它的编程语言一样,有些名字是固定使用的。
这些叫做预约语(关键字)。
变量名、函数名中不能使用跟预约语相同的名字。
通常、预约语有28个。
另外,根据C编译器的不同,预约语也多少有些不一样,请在编译器的说明书中确认。
M1.7.2 标准库的函数名、变量名及宏名不能再定义・再利用,而且不能解除定义。
参考规则MISRA-C 20.1
相关规则M1.8.2
<正确例>
#include <string.h>
void *my_memcpy(void *arg1, const void *arg2, size_t_size) {
・・・・・・・
}
<不正确例>
#undef NULL
#define NULL ((void *)0)
#include <string.h>
void *my_memcpy(void *arg1, const void *arg2, size_t_size) {
・・・・・・・
}
在标准库中,独自定义已定义的函数名、变量名、宏名,会降低程序的可读性。
note
M1.7.3 不能再定义以下划线开视的名字(变量)。
参考规则无
相关规则M1.8.2
<正确例>
无
<不正确例>
int _Max1; /* 被预约*/
int __max2; /* 被预约*/
int _max3; /* 被预约*/
struct S {
int _mem1; /* 没有被预约,但不使用*/
}
C语言规格中,以下名字已被预约。
(1)下划线后是大写英文,或2个下划线开头的名字,不管如何使用都是已被预约的。
例:_Abc,__abc
(2)1个下划线开头的所有名字
这个名字对应有file的有効范围的变量、函数的名字和Tab名,已被预约。
再次定义已被预约的名字,会导致无法保证编译器的动作。
一个下划线开头,后面是小写文字的名字,在file有効范围以外的部分中没有被预约。
但为了使规则易记,将其规定为,不使用下划线开头的所有名字。
note 不明白已被预约的意思。
(誰?)
维护性1.8不使用容易理解错的语言规格。
M1.8.1 理论Operator&&或||右侧的Operand中不能有副作用。
参考规则MISRA-C 12.4
相关规则R3.6.1 R3.6.2
<正确例>
a = *p;
p++;
/* p不依赖于p指向的内容,已经count up */
if( ((MIN < a) && (a < MAX)) {
・・・・・・・
}
<不正确例>
/* p指向的内容小于MIN时和大于MIN时,p 是否Count up的结果不一样。
(难解)*/
if (((MIN < p) && (p++ < MAX)) {
・・・・・・・
{
&&、||Operator的右式是否执行依赖于左式的条件结果。
如果右式中是Increment等有副作用的表达式、那么左式条件不同时,又可能会Increment也有可能不会,因为这样非常难理解,所以,&&、||Operator 的右式中不要采用有副作用的表达式。
note
理论Operator和关系Operator一样,在真的时候int型返回1,假的时候返回0 。
■理论Operator(Logical Operators)
理论积(AND,∧):一方为0 时返回0 ,两方都为0 以外的话,返回1。
x&&y 理论ANDOperator
(Logical AND Operator)
x||y 理论OROperator
理论和(OR,∨):一方不为0 时返回 1 ,两方都为0 时返回0 。
(Logical OR Operator)
Operand可以使用所有的Sculler型。
理论积中,第1Operand为0的话就不评估第2Operand了。
理论和中如果第1Operand不为0 就不评估第2Operand。
M1.8.2 C宏只能展开为用大括号括住的初始化子、常量、括号括住的表达式、型修饰子、记忆域Class指定子、do-while-zero 构造。
参考规则MISRA-C 19.4
相关规则M1.7.2
<正确例>
#define START 0x0410
#define STOP 0x0401
<不正确例>
#define BIGIN {
#define END }
#define LOOP_START for(;;) {
#define LOOP_END }
通过驱使宏定义,可以看成是C语言以外写的编码、而且可以大量减少编码量。
但是,把宏用于这种用途会降低可读性。
所以要用于可以防止编码失误或变更失误的地方。
do-while-zero请参考MISRA-C:2004。
M1.8.5 不能使用(0以外的)8进制常量及8进制扩展表记。
参考规则MISRA-C 7.1
相关规则M1.2.2
<正确例>
a = 0;
b = 8;
c = 100;
<不正确例>
a = 000;
b = 010;
c = 100;
0开始的常量被解释为8进制。
为了统一10进制的外观,前面不能缀0。
note
但编码(10進数のつもりで编码)发生错误时,如果有限制使用事项就比较容易发现错误。
维护性1.9使用特殊方法时,必须表明意图。
M1.9.1 如果必须故意写些什么都不作的语句时,使用Comment 、空的宏等标示出来。
参考规则MISRA-C 14.3 Indian Hill 8
相关规则M1.1.1
<正确例>
for(; ;) {
/* 等待中断*/
}
#define NO_STATEMENT
j = COUNT;
while ((-j) > 0) {
NO_STATEMENT;
}
<不正确例>
for(; ;) {
}
#define NO_STATEMENT
j = COUNT;
while ((-j) > 0);
note
在for 、while 中写本文没有的Loop时,有时初学者会因为还没有习惯;的用法,写出像for (~);
while (~);
这样,在同一行的行末加; 的Bug。
通过限制使用,可以较容易地发现失误。
维护性1.10不填写M a g i c N u m b e r。
M1.10.1 有意义的常量,作为宏定义、使用。
参考规则无
相关规则M2.2.4
<正确例>
#define MAXCNT 8
if (cnt == MAXCNT) {
<不正确例>
if (cnt == 8) {
通过宏化可以明确常量的意义,当常量用于多处时,修改一个宏就可以变更整个程序了,可以防止变更失误。
但是数据的大小不是用宏,而是用sizeof。
维护性1.11明确显示领域的属性。
M1.11.1 将只用于参照的领域声明为const。
参考规则MISRA-C 16.7
相关规则R1.1.2
<正确例>
const volatile int read_only_mem; /* 只读内存*/
const int constant_data = 10 ; /* 不用布局,只用于参照的数据*/
/* 只参照arg指向的内容*/
void func (const char *arg, int n) {
int j;
for (j = 0; j < n; j++){
put(*arg++);
}
}
<不正确例>
int read_only_mem; /* 只读内存*/
int constant_data = 10 ; /* 不用布局,只用于参照的数据*/
/* 只参照arg指向的内容*/
void func (const char *arg, int n) {
int j;
for (j = 0; j < n; j++){
put(*arg++);
}
}
只用于参照、不变更的变量,通过声明const型可以明示其不用于变更。
在编译器的最适化处理中,Object Size有可能变小,因此,把只用于参照的变量做成const型比较好。
执行单位上可以变更,但程序上只用于参照的内存,用const volatile型声明后,编译器就可以确认程序是否错误更新。
另外,在函数处理内只参照参数表示的领域时,加const可以明示函数Interface。
M1.11.2 可以被其他执行单位更新的领域声明为volatile。
参考规则无
相关规则无
<正确例>
volatile int x = 1;
while (x == 0) {
/* x不是在Loop内被变更,而是被其他执行单位变更*/
}
<不正确例>
volatile int x = 1;
while (x == 0) {
/*x不是在Loop内被变更,而是被其他执行单位变更*/
}
被volatile修饰的领域,禁止编译器进行最适化。
最适化禁止是指,即使在逻辑上是不需要的处理,也会被忠实的执行。
例如:有一个X; 的代码,在逻辑上,参考变量x是没有意义的语句,入股它没被volatile 修饰,那么通常编译器会忽视者个代码,不生成执行Object。
如果被volatile修饰了,那么会执行参照变量x(Load到寄存器)。
这个主要是因为考虑到,Load Memory时的重启之类的interface的IO register (Memory Mapping)。
嵌入式软件中需要处理控制硬件的IO Register,根据IO register的特性,需要适宜的volatile修饰。
维护性2
采用不会修改出错的方法。
程序中经常出现一种错误,就是修改一个问题的同时,带入了另一个问题。
特别是源代码制作了有一段时间了,或是修改其它技术人员写的源代码,很容易弄错发生问题。
所以我们要多下功夫,尽量减少这种修改失误。
「维护性2 」由以下的2个惯用方法构成。
维护性2.1统一明确被构造化的数据、Block。
维护性2.2局域化进入范围及相关数据。
note
如果要提高维护性,那么不只是在编码时,设计时也需要考虑。
例如,在设计软件构造时,要确保各Block(功能)的独立性。
(一定要把函数的界面做成需要的最小限度。
充分核对其他的Block(功能)的控制。
书面化函数界面的规格,编码时进行简单的函数界面变更。
)
常见的例子有,在改造以前制作的程序时追加简单的函数界面,因为设计时考虑不周,在控制没有考虑到的内容(追加了不能对应的功能)时发生问题。
这种问题的原因多是修改者对函数规格还不是十分理解,就直接进行改造。
除此之外,还有就是准备好了函数界面,但实际上却用不着。
(调试时不使用、作为应急处理的界面等,在以后很可能变成引起bug 的代码。
这种时候就要多下点功夫,例如在函数Header处写说明文等等。
)
设计阶段就需要开始考虑如何提高代码的维护性。
维护性2.1统一明确被构造化的数据、Block。
M2.1.1 用0以外的初始化队列、构造体时,必须用大括号’{ }’表示构造。
除了全部为0以外时,不能遗漏数据。
参考规则MISRA-C 9.2
相关规则R1.2.1 M4.5.3
<正确例>
int arr1[2][3] = {{0, 1, 2}, {3, 4, 5}};
int arr2[3] = {1, 1, 0};
<不正确例>
int arr1[2][3] = {0, 1, 2, 3, 4, 5};
int arr2[3] = {1, 1};
队列、构造体的初始化中,虽然最少要1对大括号就可以了,但这样会很难弄清初始化数据是怎样设定的。
所以对应构造进行逻辑化,不遗漏初始化数据的方法比较安全。
note
因为构造体型为集成体型(集合体型),所以初始化和队列一样使用大括号{ } 的初始化子。
如果指定的比Member的个数少,那么剩下的,就会对应各Member型在Memory中储存为0。
(文献List的制作例)
struct ref {
char author[40]; /* 著者*/
char title[80]; /* 标题*/
int year; /* 发行年*/
char resource[120]; /* 来源*/
} paper = {
"J. von Neumann",
"Zur Theorie der Gesellschaftsspiele",
1928,
"Mathematische Annalen"
};
M2.1.2 if、else if、else、while、do、for、switch语句的本体Block化。
参考规则无
相关规则无
<正确例>
if (x == 1){
func();
}
<不正确例>
if (x == 1)
func();
如果被if语句等控制的语句(本文)是多个语句,需要用Block圈住。
被控制的语句只有一个的话,就不需要Block化,但在程序变更时,有时会从一个语句变为多个语句,这时经常会忘了用Block圈住。
所以为了防范变更时的失误,用Block圈住各控制文的本体。
维护性2.2进入范围、相关数据局所化。
M2.2.1 只在一个函数内的使用的变量,在函数内进行变量声明。
参考规则MISRA-C 8.7
相关规则M2.2.2
<正确例>
void func1(void)
{
static int x = 0;
if (x != 0) { /* 参照上次调用时的值*/
x++;
・・・・・・・
}
}
<不正确例>
int x = 0; /* x只从func1进入*/
int y = 0; /* y只从func2进入*/
void func1(void){
if ( x != 0) { /*参照上次调用时的值。
*/
・・・・・・・
}
・・・・・・・
}
void func2(void){
y = 0; /* 毎次初始化。
*/
・・・・・・・
}
在函数内进行变量声明时,使用static可能会生效。
使用static后,有以下特征。
・静态领域被确保,领域在程序结束前都有効(没有static的通常是堆帐领域,在函数结束前有効)。
・初始化只在程序开始后进行1次,如果函数被多次调用,会保持上一次被调用的值。
因此,只是在这个函数内接近,函数结束后还想保持原值的变量,要进行static声明。
另外,在自动变量中声明大的领域,有堆帐Overflow的危险。
这种时候,就算不需要保持函数结束后的值,为了确保静态领域,也必需使用static。
但是,相对于这种做法,还是用Comment 等明确记录意图比较好(因为,有不小心使用错static的危险。
)
note
常见的问题有:制作完基本的程序后进行规格变更等,修改时使用的代码和最初制作者的程序代码不一样,进行没有准备的变量数据操作时,结果出现错误。
这种问题的原因可能是对程序的理解不足,也可能是修改者的技术不够,所以必须要防止简单的变量操作。
特别有问题的是,有时会不小心改变要修改的函数以外的函数中使用的变量,在用Cut&try修改修改程序时,为了让修改的程序运行,进行了简单的数据操作。
这样做会导致反复调试、修改函数间的相关关系,造成时间的浪费。
另外,反复地
调试、修改后可能会制作出深奥的程序,降低稳定性和维护性。
(下次规格变更时的修改会很困難。
)
为了防止这种问题,限制静态变量的开放范围是很有效的手段。
如果有什么原因要进行数据操作的话,必须要和基本程序的制作者相互审核,确认没有问题。
进行程序修改时,不能不考虑设计者的意图,而随意地变更参照范围。
M2.2.2 被同一file内定义的多个函数进入的变量,要在file scope中进行static变量声明。
参考规则MISRA-C 8.10 Indian Hill 4
相关规则M2.2.1 M2.2.3
<正确例>
/* x不被其他file进入*/
static int x;
void func1(void)
{
・・・・・・・
x = 0;
・・・・・・・
}
void func2(void)
{
・・・・・・・
if( x == 0){
x++;
}
・・・・・・・
}
<不正确例>
/* x被其他file进入*/
int x;
void func1(void)
{
・・・・・・・
x = 0;
・・・・・・・
}
void func2(void)
{
・・・・・・・
if( x == 0){
x++;
}
・・・・・・・
}
global 变量的数量越少,理解程序整体时的可读性就越高。
为了不增加Global变量,尽量使用static记忆Task指定子,不让它变成Global变量。
note
有効范围(Scope)
变量名,函数名,标签,tab,构造体和枚举体的Member这些「标识符」(Identifiers),根据在程序内声明的位置不同,在程序内可以使用的范围也不一样。
可以使用的范围叫有効范围(Scope),可以使用时,其标识符为可视(visible)的。
■有効范围(Scope)
○函数Scope(函数有効范围,Function Scope)
被声明函数内。
只有Lable有有効范围。
○Prototype・Scope(函数原型有効范围,Function Prototype Scope)
Prototype声明内。
Prototype声明内的形式参数有有効范围。
○file・Scope(file有効范围,File Scope)
这个source file内。
在全部的Block外侧被声明,或者不是形式参数时。
○Block・Scope(Block有効范围,Block Scope)
Block { } 内。
在Block { } 内被声明,或是函数定义的形式参数。
有的Scope中含有其它的Scope,这时前者是后者的外部有効范围(outer scope),后者是前者的内部有効范围(inner scope)。
外部侧中声明的变量在内部侧中也有効(可视)。
当内部侧中有相同名字的变量声明时,内部侧中,在内部声明的有効,外部侧中声明的那个在内部侧中变为隐藏状态(hidden)。
(即:内部侧的变量隐藏外部侧的变量。
)
M2.2.3 只被file中定义了的函数调用的函数叫static函数。
参考规则MISRA-C 8.10 Indian Hill 4
相关规则M2.2.2
<正确例>
/* func1不被其他file进入*/
static void func1(void)
{
・・・・・・・
}
void func2(void)
{
・・・・・・・
func1();
・・・・・・・
}
<不正确例>
/* func1不被其他file进入*/
void func1(void)
{
・・・・・・・
}
void func2(void)
{
・・・・・・・
func1();
・・・・・・・
}
global 变量的数量越少,理解程序整体时的可读性就越高。
为了不增加Global变量,尽量使用static记忆Task指定子,不让它变成Global变量。
note
因为规格变更等修改程序时,可以把不能进行单独调用的函数(进行函数初始化等)设置成无法错误调用。
维护性3
制作简单的程序。
要让软件容易维护,最好的方法就是用简单的书写方式写软件。
C语言通过分Source file、分函数来进行软件的构造化。
通过顺序・选择・反复这3个来表现程序构造的构造化编程也可以使软件简单化。
请在活用软件构造化、制作简单的软件上多费些心思。
另外,反复处理、代入、演算等在书写方式上算是很难维护的类型,所以要小心使用。
「维护性3 」由以下4个惯用方法构成。
维护性3.1进行构造化编程。
维护性3.2一个语句中只能有一个副作用。
维护性3.3分别编写目的不同的表达式。
维护性3.4不使用复杂的指针演算。
note
程序维持对应性比较好。
例如,将set和reset,start和stop等有相关性的函数局所化。
构造化编程中,是把程序以功能为单位划分的。
功能的最小单位叫模块。
如果模块间来往的信息很多的话,会停止划分,将其连接成1个模块(把模块的input和output弄成1个)。
就算模块只有1行Statement,只要能明确了解其功能的话,也把它做成独立模块。
构造化是指把大的功能细分化、把小的功能有机集合。
具体来说,就是把程序分成子程序,能一眼就看出处理的流程。
现在的编程工作是使用Display进行的,因此,一个模块的大小目标定在20行以内。
但是,它最大的目的毕竟是区分功能,不能太拘泥于子程序的行数。