编码规则(可维护性)
第2章 维护性 第2章 维护性
2006/11/10 V1.00
大多嵌入式软件开发中,都会在制作完成的软件上进行维护作业。
维护的原因各种各样,例如:
? 发布的软件中发现Bug,需要修改。
? 对应产品的市场要求,以既存软件为基础,追加新的功能。
等等。
像这样,在制作好的软件上加工,要尽量避免错误,有效的进行。
系统界管这叫维护性。
在此,整理了维护、提高嵌入式软件源代码维护性的一些惯用方法。
? 维护性,???意识到其他人也会看你的代码。
? 维护性,???使用不会改错得方法。
? 维护性,???把程序尽量简单化。
? 维护性,???统一编码方法。
? 维护性,???使用便于测试的编码方法。
? 维护性,???Uniden,株,Know-how集。
1/72 1
维护性, 维护性,
意识到其他人也会看你的代码。
在制作源代码时考虑到,它会被制作者以外的技术者再利用或维护。因此,源代码要使
用容易理解的表现方式。
「维护性1 」有以下,,个惯用做法。
维护性,., 不保留不使用的代码。
维护性,., 不使用麻烦,杂乱的写法。
维护性,., 不使用特殊的写法。
维护性,., 演算的优先顺序明确易懂。
维护性,., 不省略取得函数地址的演算、比较演算。
维护性,., 一个领域用于一个目的。
维护性,., 不重复使用名字。
维护性,., 不使用容易理解错的语言规格。
维护性,., 在特殊的方法中写明意图。
维护性,.10 不掩埋Magic Number。
维护性,.11 明示领域属性。
维护性,.12 不编译的语句也要正确记述。
2/72 2
维护性1.1 不遗留不使用的代码。 维护性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。
3/72 3
维护性1.2 不使用麻烦,杂乱的写法。 维护性1.2 。
M1.2.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
4/72 4
维护性1.3 不使用特殊的方法。 维护性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语句。
5/72 5
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型,明确记述数据型会看起来更方便。
6/72 6
维护性1.4 演算的优先顺序明确易懂 维护性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语言规则的理解度不是很够。所以,下功夫制作简单易懂的代码,让这些工程师也能正确理解代码是很重要的。
7/72 7
C语言中,Operator的优先顺序如下。
Operator 的优先顺序
优先程度 Operator 结合规则
15 ( ) [ ] -> ?
14 Sizeof,型, & - + - - ++ ~ !
13 * / %
12 + -
11 << >>
10 < <= > >=
9 == !=
左?右 8 &
7 ^
6 |
5 &&
4 ||
3 ? :
2 >>= <<= ^= &= %= /= *= -= += =
1 ,
这个标的纵轴表示优先顺序的等级,数字越大,优先度越高,。结合规则表示的是,优先顺序同等级时的演算顺序。
8/72 8
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。取,个Operand的Operator叫「单项Operator」,Unary Operator,,取,个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;'
9/72 9
维护性1.5 不省略取得函数地址的演算、比较演算。 维护性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的函数的指针。
10/72 10
维护性1.6 1个领域只用于一个目的 维护性1.6 1个领域只用于一个目的
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
为了减少堆帐使用量,有时会再次使用自由变量。最近的编译器中就算准备各自的自由变量也不能最
适化,所以没多大意义。另外,再次使用自由变量会导致变量的存在期间长,所以堆帐使用量也可能
会増加。
11/72 11
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中禁止使用共用体,因为依赖于处理系统的处理很多,所以尽量不用的好。
12/72 12
维护性1.7 不再次使用同一个名字。 维护性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
用于变量、函数命名的规则说明。
13/72 13
,,变量、函数的名字中能使用的文字有英文,大?小,、数字、Under Score,_,等共计63个文字。这些文字可以自由组合形成名字。但是,最初的文字一定要是英文或Under Score。
,,名字取多长都行,但通常有効的文字数只到第8个文字。另外,名字不可以只是一个Under Score。 ,,:语言中,明确区分了英语大写和小写。通常,C语言中是用小写来写程序。大写用于表示特殊意义。
,,C语言中,和其它的编程语言一样,有些名字是固定使用的。这些叫做预约语,关键字,。变量名、函数名中不能使用跟预约语相同的名字。通常、预约语有28个。另外,根据C编译器的不同,预约语也多少有些不一样,请在编译器的说明书中确认。
14/72 14
C语言的预约语
预约语 意义
auto 自动变量的声明
char 文字型变量的声明
double 倍精度浮动小数点型变量的声明
extern 外部变量的声明
float 浮动小数点型变量的声明 数据型 int 整数型的声明
和 long 倍精度整数型的声明 记忆class register 寄存器变量的声明
short 短い整数型的声明
static 静的变量的声明
struct 构造体的声明
typedef 型的定义
union 共用体的声明
unsigned 符号无整数的声明
break 从 Loop跳出
case switch:case的定义
continue 返回以下的重复内容 控制 default switch:default的定义
do do Loop
else if:else
for for Loop
goto 无条件分歧
if 条件判定
return 从函数返回
switch 多条件分歧
while while Loop 其他 entry 无意义
sizeof 数据型的长度
15/72 15
M1.7.2
库的函数名、变量名及宏名不能再定义?再利用,而且不能解除定义。 参考规则 MISRA-C 20.1
相关规则 M1.8.2
,正确例,
#include
void *my_memcpy(void *arg1, const void *arg2, size_t_size) {
,,,,,,,
}
,不正确例,
#undef NULL
#define NULL ((void *)0)
#include
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有効范围以外的部分中没有被预约。但为了使规则
易记,将其规定为,不使用下划线开头的所有名字。
16/72 16
note 不明白已被预约的意思。,誰,,
17/72 17
维护性1.8 不使用容易理解错的语言规格。 维护性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,
x&&y 理 论ANDOperator 理论积 (AND,?),一方为 0 时返回 0 ,两方都为 0 以外的话,返回 1。
,Logical AND Operator,
x||y 理论OROperator 理论和 (OR,?),一方不为 0 时返回 1 ,两方都为 0 时返回0 。
,Logical OR Operator,
Operand可以使用所有的Sculler型。
理论积中,第, Operand为 0的话就不评估第, Operand了。理论和中如果第, Operand不为 0 就不评估第, Operand。
18/72 18
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。
19/72 19
M1.8.5 不能使用,:以外的,8进制常量及8进制扩展表记。 参考规则 MISRA-C 7.1
相关规则 M1.2.2
,正确例,
a = 0;
b = 8;
c = 100;
,不正确例,
a = 000;
b = 010;
c = 100;
:开始的常量被解释为8进制。为了统一10进制的外观,前面不能缀:。
note
但编码,10進数のつもりで编码,发生错误时,如果有限制使用事项就比较容易发现错误。
20/72 20
维护性1.9 使用特殊方法时,必须表明意图。 维护性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。通过限制使用,可以较容易地发现失误。
21/72 21
维护性1.10 不填写Magic Number。 维护性1.10 不填写Magic Number。
M1.10.1 有意义的常量,作为宏定义、使用。
参考规则 无
相关规则 M2.2.4
,正确例,
#define MAXCNT 8
if (cnt == MAXCNT) {
,不正确例,
if (cnt == 8) {
通过宏化可以明确常量的意义,当常量用于多处时,修改一个宏就可以变更整个程序了,可以防止变更
失误。
但是数据的大小不是用宏,而是用sizeof。
22/72 22
维护性1.11 明确显示领域的属性。 维护性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。
23/72 23
M1.11.2 可以被其他执行单位更新的领域声明为volatile。
参考规则 无
相关规则 无
,正确例,
volatile int x = 1;
while (x == 0) {
/* , 不是在Loop内被变更,而是被其他执行单位变更 */
}
,不正确例,
volatile int x = 1;
while (x == 0) {
/*, 不是在Loop内被变更,而是被其他执行单位变更 */
}
被volatile修饰的领域,禁止编译器进行最适化。最适化禁止是指,即使在逻辑上是不需要的处理,也会被忠实的执行。例如:有一个X; 的代码,在逻辑上,参考变量x是没有意义的语句,入股它没被volatile修饰,那么通常编译器会忽视者个代码,不生成执行Object。如果被volatile修饰了,那么会执行参照变量,,Load到寄存器,。这个主要是因为考虑到,Load Memory时的重启之类的interface的IO register,Memory Mapping,。嵌入式软件中需要处理控制硬件的IO Register,根据IO register的特性,需要适宜的volatile修饰。
24/72 24
维护性2 维护性2
采用不会修改出错的方法。
程序中经常出现一种错误,就是修改一个问题的同时,带入了另一个问题。特别是源代码制作了有一段时间了,或是修改其它技术人员写的源代码,很容易弄错发生问题。 所以我们要多下功夫,尽量减少这种修改失误。
「维护性2 」由以下的2个惯用方法构成。
维护性,., 统一明确被构造化的数据、Block。
维护性,., 局域化进入范围及相关数据。
note
如果要提高维护性,那么不只是在编码时,设计时也需要考虑。例如,在设计软件构造时,要确保各Block,功能,的独立性。,一定要把函数的界面做成需要的最小限度。充分核对其他的Block,功能,的控制。书面化函数界面的规格,编码时进行简单的函数界面变更。,
常见的例子有,在改造以前制作的程序时追加简单的函数界面,因为设计时考虑不周,在控制没有考虑到的内容,追加了不能对应的功能,时发生问题。这种问题的原因多是修改者对函数规格还不是十分理解,就直接进行改造。除此之外,还有就是准备好了函数界面,但实际上却用不着。,调试时不使用、作为应急处理的界面等,在以后很可能变成引起bug的代码。这种时候就要多下点功夫,例如在函数Header处写说明文等等。,
设计阶段就需要开始考虑如何提高代码的维护性。
25/72 25
维护性2.1 统一明确被构造化的数据、Block。 维护性2.1
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"
};
26/72 26
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圈住各控制文的本体。
27/72 27
维护性2.2 进入范围、相关数据局所化。 维护性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修改修改程序时,为了让修改的程序运行,进行了简单的数据操作。这样做会导致反复调试、修改函数间的相关关系,造成时间的浪费。另外,反复地
28/72 28
调试、修改后可能会制作出深奥的程序,降低稳定性和维护性。,下次规格变更时的修改会很困難。,
为了防止这种问题,限制静态变量的开放范围是很有效的手段。如果有什么原因要进行数据操作的话,
必须要和基本程序的制作者相互审核,确认没有问题。
进行程序修改时,不能不考虑设计者的意图,而随意地变更参照范围。
29/72 29
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,
30/72 30
变量名,函数名,标签,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,。,即:内部侧的变量隐藏外部侧的变量。,
31/72 31
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
因为规格变更等修改程序时,可以把不能进行单独调用的函数,进行函数初始化等,设置成无法错误调用。
32/72 32
维护性3 维护性3
制作简单的程序。
要让软件容易维护,最好的方法就是用简单的书写方式写软件。
:语言通过分Source file、分函数来进行软件的构造化。通过顺序?选择?反复这3个来表
现程序构造的构造化编程也可以使软件简单化。请在活用软件构造化、制作简单的软件
上多费些心思。另外,反复处理、代入、演算等在书写方式上算是很难维护的类型,所以
要小心使用。
「维护性3 」由以下4个惯用方法构成。
维护性,., 进行构造化编程。
维护性,., 一个语句中只能有一个副作用。
维护性,., 分别编写目的不同的表达式。
维护性,., 不使用复杂的指针演算。
note
程序维持对应性比较好。例如,将set和reset,start和stop等有相关性的函数局所化。
构造化编程中,是把程序以功能为单位划分的。功能的最小单位叫模块。如果模块间来往的信息很多的
话,会停止划分,将其连接成1个模块 (把模块的input和output弄成1个)。就算模块只有1行Statement,
只要能明确了解其功能的话,也把它做成独立模块。
构造化是指把大的功能细分化、把小的功能有机集合。具体来说,就是把程序分成子程序,能一眼就看出
处理的流程。现在的编程工作是使用Display进行的,因此,一个模块的大小目标定在,:行以内。但是,
它最大的目的毕竟是区分功能,不能太拘泥于子程序的行数。
33/72 33
进行构造化编程。维护性3.1 。 维护性3.1 。
M3.1.1 反复语句中,最多只能有1个用于结束 Loop的break语句。
参考规则 MISRA-C 14.6
相关规则 M2.2.2
,正确例,
end = 0;
for ( j = 0; Loop的继续条件 && !end; j++) {
反复处理,;
if (结束条件, || 结束条件,) {
end = 1;
} else {
反复处理,;
}
}
或
for ( j = 0; Loop的继续条件 && !end; j++) {
反复处理,;
if (结束条件, ||结束条件,) {
break;
} else {
反复处理,;
}
}
,不正确例,
for ( j = 0; Loop的继续条件 && !end; j++) {
反复处理,;
if (结束条件,) {
break;
} else if (结束条件2) {
break;
} else {
反复处理,;
}
}
要下功夫使程序理论不变得复杂。如果没有break语句就必须使用flag的话,那还是不要使用flag,使用
、使用end flag的例子可能会使程序变得复杂,使用时要注意,。 break比较好。,正确例中
note
Loop的内容很长时会降低可读性,写了很多break的程序中,Loop内容通常都很长。遇到这样的函数
时,把函数分开,单纯化一下比较好。
虽然不是必须的,但是,尽可能考虑一下比较好。
34/72 34
M3.1.2 goto语句只用于从多重 Loop中出来和错误处理有分歧时。
参考规则 无
相关规则 无
,正确例,
if (err != 0) {
goto ERR_RET;
}
,不正确例,
j = 0;
LOOP:
重复处理;
j++;
if ( Loop的继续条件) {
goto LOOP;
}
要下功夫让程序理论不变得复杂。目的并不是消除goto语句,而是为了避免程序复杂的Null,无法从上往
下读的Null,的手段,消除不要的goto,这是非常重要的。 当然,有时候使用goto语句也可以提高可读性。
在编程时要考虑如何把理论简单地表现出来。
note
使用goto语句的程序很少。原则上是禁止使用,制作简单的程序。,与其使用goto语句,不如在写代码
上多下些功夫。,
35/72 35
M3.1.3 不使用continue语句。
参考规则 MISRA-C 14.5
相关规则 无
,正确例,
for (j = 0; Loop的继续条件 ; j++) {
反复处理1;
if ( !继续条件, ){
反复处理,;
}
}
,不正确例,
for (j = 0; Loop的继续条件 ; j++) {
反复处理1;
if ( 继续条件, ){
continue;
}
反复处理,;
}
要下功夫让程序理论不变得复杂。目的并不是消除continue语句,而是为了避免程序复杂的Null,无法从
上往下读的Null,的手段,消除不要的continue,这是非常重要的。 当然,有时候使用continue语句也可
以提高可读性。在编程时要考虑如何把理论简单地表现出来。
note
如果使用continue语句,会导致可读性非常低下。所以应该在代码编写上下功夫,禁止使用continue
语句。(编写程序时,可以完全不要continue语句。)
36/72 36
M3.1.4 不用Break语句结束switch语句的case节、default节时,插入<