为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

编码规则(可维护性)

2017-10-30 50页 doc 140KB 22阅读

用户头像

is_219945

暂无简介

举报
编码规则(可维护性)编码规则(可维护性) 第2章 维护性 第2章 维护性 2006/11/10 V1.00 大多嵌入式软件开发中,都会在制作完成的软件上进行维护作业。 维护的原因各种各样,例如: ? 发布的软件中发现Bug,需要修改。 ? 对应产品的市场要求,以既存软件为基础,追加新的功能。 等等。 像这样,在制作好的软件上加工,要尽量避免错误,有效的进行。 系统界管这叫维护性。 在此,整理了维护、提高嵌入式软件源代码维护性的一些惯用方法。 ? 维护性,???意识到其他人也会看你的代码。 ? 维护性,???使用不会改错得方...
编码规则(可维护性)
编码规则(可维护性) 第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节时,插入<>中的Comment 。 参考规则 MISRA-C 15.1 15.2 Indian Hill 9 相关规则 R3.5.2 ,正确例, dd = 0; switch (status) { case A: dd++; /* FALL THROUGH */ case B: ,,,,,,, ,不正确例, dd = 0; switch (status) { case A: dd++; case B: ,,,,,,, /* 可以继续case B的处理,但没有Comment */ 比较容易编码失误的代表例之一就是,:语言的switch语句中忘了写break语句。 应该要避免使用不需要的没有break的case语句。如果没有break语句,继续进行下一个case的处理的话,一定要插入Comment说明没有Break语句也不会有问题。如何插入就看Project的单位来定。 note 不插入break时,插入Comment ,这样在代码审核等时比较容易发现错误。另外,在case语句很多的程序中,有时很难发现因为编码失误而忘了插入的break语句时,调试中也有効。 在default语句中记录Break比插入Comment 好。 37/72 37 维护性3.2 一个语句中只能有一个副作用。 维护性3.2 M3.2.1 逗号表达式在for语句的初始化式及更新式以外时不使用。 参考规则 MISRA-C 12.10 Indian Hill 10 相关规则 M3.3.1 ,正确例, a = 10; b = 5; for ( j = 0, k = 10; j < 10; j++, k--) { ,,,,,,, } ,不正确例, a = 10, b = 5; for ( j = 0, k = 10; j < 10; j++, k--) { ,,,,,,, } 使用逗号形式会变得复杂。但是,for语句的初始化式及更新式是 Loop前后应该实施的处理的,使 用逗号表达式会比较清晰易懂。 38/72 38 M3.2.2 1个语句中不要写多个代入。把相同值代入多个变量的除外。 参考规则 MISRA-C 8 相关规则 无し ,正确例, a = b = 0; ,不正确例, y = (x += 1) + 2; c = (a++) + (b++); ,,,,,,, } 代入中除了单纯代入(=)还有复合代入(+=,-=)。一个语句中可以进行多个代入,但是会降低可读性。所以1 个语句中,应该只用1各代入。 note 如果制作的程序很复杂,技术能力不够的工程师很难理解,那么是写程序的人的错。因为实际的制造 工程中有各种技能的工程师共同作业,制作谁都能看得懂的代码是很重要的。 39/72 39 维护性3.3 分开写目的不一样的表达式。 维护性3.3 分开写目的不一样的表达式。 M3.3.1 for语句的3各表达式中,必需只写 Loop控制相关的内容。 参考规则 MISRA-C 13.5 相关规则 M3.2.1 M3.3.2 ,正确例, for (j = 0; j < MAX ; j++) { ,,,,,,, k++; } ,不正确例, for (j = 0; j < MAX ; j++, k++) { ,,,,,,, } M3.3.2 for Loop中,作为反复Counter的数值变量不能在 Loop的本体内变更。 参考规则 MISRA-C 13.6 相关规则 M3.3.1 ,正确例, for (j = 0; j < MAX ; j++) { ,,,,,,, } ,不正确例, for (j = 0; j < MAX ;) { ,,,,,,, j++; } 40/72 40 维护性3.4 不使用复杂的指针演算。 维护性3.4 不使用复杂的指针演算。 M3.4.1 不使用3阶段以上的指针指定。 参考规则 无 相关规则 无 ,正确例, int **p; typedef char **strptr_t; strptr_t q; ,不正确例, int ***p; typedef char **strptr_t; strptr_t *q; 要理解3阶段以上的指针的值的変化很困难,有损维护性。 note MISRA-C 17.5中规定不能使用2阶段以上的指针指定。 41/72 41 维护性4 维护性4 统一书写方式。 最近的程序开发都是多个人从业人员共同开发。这时,如果开发人员都用各自不同的书写方式的话,之后以确认各内容为目的的审核就会做的很辛苦。而且,如果变量的命名、file内记载的信息内容、顺序不一致很散乱的话,那肯定是很容易产生误解、错误的。因此,在同一个Project或组织内,要尽力统一代码的书写方式。 「维护性4 」由以下7个惯用方法构成。 维护性,., 统一编码风格。 维护性,., 统一Comment的书写方式。 维护性,., 同一命名方法。 维护性,., 统一file内的书写内容和顺序。 维护性,., 统一声明的书写方式。 维护性,., 统一Null指针的书写方式。 维护性,., 统一前处理命令的书写方式。 42/72 42 维护性4.1 统一编码风格。 维护性4.1 统一编码风格。 M4.1.1 <<大括号”{“、缩进、插入空白等风格相关的规定。>> 参考规则 无 相关规则 无 为了让代码看起来方便,在Project中统一编码风格是很重要的。 以下是应该确定的项目的说明。 在此推荐K&R风格的缩进、插入空格的方法。 ,,, 大括号的位置。 参照K&R风格的缩进,插入空格的方法 ,,, 缩进,indentation, 参照K&R风格的缩进,插入空格的方法 ,,, 插入空格 参照K&R风格的缩进,插入空格的方法 ,,, 继续行的换行位置。 把Operator放在继续行的开头 例 x = var1 + var2 + var3 +var4 + var5 + var6 + var7; if (var1 == var2 && var3 == var4 && var5 == var6) ?K&R风格缩进、插入空格的方法 介绍K&R风格の缩进、空白的使用方法。模仿K&R风格的最大好处是,采用K&R风格的技术人员多。另外, 语句中的?×在 K&R 风格中是用于表示是否等同,×是表示错误的意思。 *** 规则 *** ?缩进的宽度为,个字符。 while ((c = getchar()) != EOF) { if (c < ' ') { putchar('^'); c += '@'; } putchar(c); } 缩进幅度有多种风格,大部分是,:,个字符。K&R 第1版中是5个空格,但使用2个空格的人也很多, 特别是在 GNU 的代码中很醒目。Borland C++ 的Reference Manual的程序例中是,个字符。如果使用能 指定TAB 宽度的Editor的话,设定TAB=4 进行编辑比较好。 ?在预约语if、for、while、switch 等后、「() 前插入空格。在「」」后、「{} 前插入空格。 × if(c == EOF) ( .. /* if 后没有紧接着空格 */ 43/72 43 × if (c == EOF){ .. /* { 前没有空格 */ ? if (c == EOF) { .. ?在函数名或宏后接续「()时,不插入空白。 × printf ("hello world\n"); ? printf("hello world\n"); 像这样把if、for、…等和函数的书写方式区分开来,就很容易区别了,用Editor、工具查找时也会变轻松。 ?在「() 和 它后面的文字间不要加入空格。「」」和它前面的文字间也不要加入空格。 × if ( c == EOF ) { .. ? if (c == EOF) { .. 插入空格好像也可以,但是太多空格的话,反而会变得看起来不方便。连续使用括号时,像下面这样写的话,就会延长结构。 while ( ( c = getchar() ) != EOF ) ?逗号,,,、分号,;,后,原则上是插入1个空格。 × printf("i = %d\n",i); ? printf("i = %d\n", i); 这是: 的书写方式中,用英文写时的普遍方法。但是,在英文输入法下,在分号后输入,个空格也是很普遍的。 ?在单项Operator和使用它的表达式等之间,不能有空格。 × i ++; ? i++; 单项Operator的优先顺序很高,被认为有高连接性的意思,要连着写。 ?,项Operator的两侧,原则上要有1个空白。但是,如果有特殊原因的话,也不是非要这样。 ? sum=a+b; ? sum = a + b; ,项Operator比单项Operator优先顺序低,所以特别是和单项Operator混在一起时,这样写会更容易读。 K&R的List中,也有些,项Operator的两侧没有空格的例子。可能是为了在演算结果中设置重点,这样的例子很多。 sum = a++ + -b; 例如、 sum = a-b; 这样还行, sum = a--b; 但,变成 sum = a---b; 的话,就不知道是什么意思了。 ?用于进入构造体的Member的Period,.,或 -> 的两侧不插入空格。 44/72 44 × cursor . x = newptr -> h; ? cursor.x = newptr->h; 这是因为"."、"->"的优先顺序高,不插入空白反而能明确表现连接性。 ?,项Operator ?: の、「?」、「:」的两侧插入空格。 × abs = (a >= 0)?a:-a; ? abs = (a >= 0) ? a : -a; 这大概也是因为优先顺序的关系。 ?Cast Operator中使用的括号「」」后不插入空白。 × i = (int)c; ? i = (int) c; ?return 后不使用不必要的 () 。 × return (0); ? return 0; K&R 的第,版中使用的是、return (表达式); 这样的写法。使用括号的原因不是很清楚。最常听到的原因是,用括号会比较容易看懂,我不是很理解为什么用括号比较容易看懂,我觉得反而会变得不好看。到了第,版后,return 后的括号就没了。 ?for 语句的写法,'{' '}' 的位置, for (expression1; expression2; expression3) { .. } for (;;) { /* 无限循环时 */ .. } 隔开for 中的表达式的分号后有空格。只有无限循环时,可以没有空格全部挤在一起。这也类似于惯用句。 像下面这样,for 语句的本体不是复合语句时,以及当它是空文时,不能写在同一行内,必须写到下一行。特别是不可以把表示空文的分号写到同一行,会变成混乱的源头。 for (expression1; expression2; expression3) ; ?while 语句的写法,'{' '}' 的位置, while (expression) { ... } 和for 语句一样,当while语句的本体不是复合语句时,以及当它是空文时,不能写在同一行内,必须写到下一行。 while (expression) ; ?do while 语句的写法,'{' '}' 的位置, do { .. } while (expression); ?switch语句的写法,'{' '}' 的位置, 45/72 45 switch (expression) { case constant-expression: .. break; default: .. break; } 我想这个是结构有异议的地方。case 标签和switch语句在写同一位置。default 也一样。除此以外的写法 还有、把case 写的比 switch 稍后一点。在K&R 中, switch 和 case 是同一高度。在写Lable时,解除 一个缩进,把它弄显眼一点,记住就行了。 ?if语句的写法,'{' '}' の位置, if (expression) { ... } else if { ... } else { ... } 就像之前所说的,这些括号的写法基本是从关键字(for, while, do, switch, if)的开头文字一直往下延长,直 到最初出现 '}' 的地方,中间是这个关键字对应的有効范围。有効范围内部有一段缩进较深的,这是用于 进行区分,这样就能简单的识别。 ?函数定义的例子。,'{' '}' 的位置, unsigned int foo() { ... } 实际上,左边不插入空格,从最左开始写。函数类型写在函数名的同一行。这样的话,使用grep 这些工具 以行为单位进行函数名查找时,会同时显示类型,很方便。 ?用空白行分离变量定义部分和以后的部分。 unsigned int foo(int count) { int total = 0; while ((count & 1) == 0) { total++; count >>= 1; } return total; } ※ 当然,其他的地方最好也按照意义在适当的地方插入空格进行分隔。 在上面的例子中,int total = 0; 行后面是空白行。 ?队列的 [] 前后不插入空格。 × array [10] ? array[10] ?表达式或语句的 ; 前不插入空白。 46/72 46 × char keyword[10] ; ? char keyword[10]; ?变量声明的方法的例子。 FILE *fp; char *str; int i, j; 类型名后插入1个空格。不用TAB 整理。 但是有很多人会使用TAB,整理变量的开头。 指针按之前所说的那样写。不要写成下面这样。 char* str; K&R风格的风格介绍到此为止。 K&R风格的程序惯用法是指 制造出:语言的,个人写的、是:语言的Bible。名字是以这,个人的名字的头文字「,,,」来命名的。 编程语言:,第,版, ,,,,カーニハン,,,,,リッチー著、石田晴久訳、共立出版、,,,, 编程语言:,第,版, ,,,,カーニハン,,,,,リッチー著、石田晴久訳、共立出版、,,,, 47/72 47 维护性4.2 统一编码风格 维护性4.2 统一编码风格 M4.2.1 <<规定File Header Comment 、函数Header Comment 、行末Comment 、 BlockComment 、Copy Write等书写方式相关的规则。>> 参考规则 无 相关规则 无 为了能熟练的写Comment,提高程序的可读性。为了更方便阅读,必需统一书写方法。 另外,制作用于源代码维护?调查的文档时,有生成文档的工具。要灵活使用这些工具的话,需要配套的 书写方式。文档生成工具,一般是按一定的comment规则说明变量、函数,从源代码中反映到文档。应 该要先调查工具的规格,再决定Comment 。 以下的既存的编码规则、书籍等中也有Comment 的书写方式相关的介绍。 ? 代表的Comment 的书写方式 ,,,Indian Hill 编码规则中,记述了以下的Comment 规则。 ? 在记述Block Comment 数据构造或十进制时使用。形式是:在第,位写/,所有的第,位和第,位中 写*/。(grep^中可以抽出Block Comment)。 例 /* 写Comment * 写Comment */ ?Comment 的位置 - 函数中的Block Comment 在合适的位置写,比如在下一行的缩进位置写。 - 行末的Comment 用Tab离远一点写。如果有多个这样的Comment ,在相同的缩进位置写。 ,,, GNU编码规则中,记述了以下的Comment 规则。 ? 记述语言 英语 ? 记述位置和内容 -程序的开头 所有的程序,都是以对程序进行简单介绍的Comment 开头。 -函数 每个函数中都插入Comment。 说明这是干什么的函数、参数,值、意义、用途,、返回值 -endif 除了不是嵌套条件的短条件,在所有#endif的行末插入Comment ,明确条件。 -工具的写法 在Comment 语句的结尾,设置2个空格。 48/72 48 ,,,“变成惯用法”中记述了以下Comment 规则。 ?记述位置 记述函数和Global数据相关的。 ?其他的惯用法 -不需要把理所当然的事情都写上去。 -不和代码产生矛盾。 -写的清楚明白,不引起混乱。 ?具体的注意事项 ?定义函数时,要写清楚这是一个干什么的函数、在什么时候会返回怎样的值。 ?对应变量的Comment,要写清楚这个值在什么时候有着怎样的含义。 ?反复处理(for, while等)中,要写清楚在什么条件下反复进行、或结束反复。 ?条件判断(if, switch等)中,要写清楚在什么条件下执行处理。 49/72 49 维护性4.3 统一命名方法。 维护性4.3 统一命名方法。 M4.3.1 <<规定外部变量、内部变量等命令相关的规则。>> 参考规则 无 相关规则 M1.7.1 M1.7.2 M1.7.3 M4.3.2 <<规定file名命名相关的规则。>> 程序的易读性和命名方法有很大的关系。命名有各种各样的方式,重要的是統一性和易懂性。 命名方法中规定有以下几个项目。 ? 名字整体的指針 ? file名,包括folder名或地址名,的命名方法 ? Global名字和Local名字的命名方法 ? 宏名的命名方法等 ? 代表的行的命名规则 ,,,Indian Hill编码规则 ? 为了确保下划线前后连接的名字是系统用的,不使用这样的名字。 ? 被#define的常量名全部用大写。 ? enum定义的常量,开头或全部文字用大写。 ? Global中的,为了知道属于哪个模块,用相同的接头词。 ? file名的头文字为英文,之后的文字在8个英数字以下,扩张名除外,。 ? 库的Header file名中,要避免相同名字。 ,,,GNU编码规则 ? Global变量,函数名中不使用太短的名字。使用有英语意思的名字。 ? 名字中用下划线分割词语。 ? 大写字母只使用宏和enum常量和遵从一定规则的名字的接头词,通常只使用英文小写的名字。 ,,,“编码惯用法” ? Global中使用简单易懂的名字,Local中使用短一点的名字。 ? 有关联性的,名字要体现出其相关性、突显其不同的地方。 ? 函数名基本上用能动的动词,没什么特别问题的话,后面缀上名词。 ?命名方法的具体例 ,,,Scope(通用范围)的基准命名规则的例子。 ?函数名以大写字母开头,变量以小写字母开头 ?外部变量的名字以「g」开头 50/72 50 ?构造体(或Class)的Member变量名以「m」开头 ?自动变量名以「the」开头 ?只用于参照的参数名字以「in」开头,可重写的参数名字以「out」开头,两者都可的以「io」开头。 ,,,Hungarian Notation的案例例 ?Hungarian Notation是指在变量名前放置显示类型的前置文字,Prefix,这样的做法。 ?对Hungarian Notation持怀疑意见的人很多。(像暗号一样的记号) ?前置文字的例子 n 不用管Size的有符号的整数型(int) u 不用管Size的没有符号的整数型(unsigned int)、或没有符号的整数 c char型 sh short型 l long型 f float型 d double型 p 指针 a 队列 sz 文字列 by BYTE型 b bool型 fn 函数型 m_ 构造体、Class的Member变量 C Class名 S 构造体名 E 枚举名 跟上述的前置文字组合后,容易变得意义不明。 如果变更变量类型的话,就要修改全部的记述。 ,,,名字的结束方法 结束多个单词构成的名字中的单词时,要统一为:用下划线结束,或是把单词的第一个字母规定为大写 这两种方法中的一个。 51/72 51 维护性4.4 统一file内的记述内容和记述顺序。 维护性4.4 统一file内的记述内容和记述顺序。 M4.4.1 <<规定Header file中记述的内容,声明、定义等,及其顺序。>> 参考规则 无 相关规则 无 在多个地方记述的话,变更时容易出现失误,所以,通用的内容写在Header file中。Header file中记述多 个source file中共同使用的宏定义、构造体?共用体?枚举型的tab声明、typedef声明、外部变量声明、函数 Prototype声明。 按以下顺序记述。 ,?file header comment ,?system header comment ,?user make header comment ,?#define marco ,?#define function marco ,?typedef defime,对应int或char这样的基本类型的类型定义, ,?enum tab定义,同时执行typedef, ,?struct/union tab定义,同时执行typedef, ,?extern变量声明 ,:?函数Prototype声明 52/72 52 M4.4.2 <<规定Source file中记述的内容,声明、定义等,及其顺序。>> 参考规则 无 相关规则 无 常见的记述顺序为:声明了共用的Source file取用,#include,、只在自己C source file中使用的的宏、tab、 型,typedef,的定义或声明。 以下に顺序を記述する。 ,?file header comment ,?system header comment ,?user make header comment ,?只在自己file内使用的#define宏 ,?只在自己file内使用的#define函数宏 ,?只在自己file内使用的typedef定义 ,?只在自己file内使用的enum tab定义 ,?只在自己file内使用的struct/union tab定义 ,?file内共用的static变量声明 ,:?static函数声明 ,,?函数定义 ,,,、,,,要注意不要取入了不要的东西。 ,,,:,,,尽量不要记述。 53/72 53 M4.4.3 使用、定义外部变量、函数,只在file内使用的函数除外,时,要包括记述了声明 的Header file。 参考规则 MISRA-C 8.4 Indian Hill 2.3 GNU 相关规则 无 ,正确例, --- my_inc.h --- extern int x; int func(int); ----------------- #include “my_inc.h” int x; int func(int in) { ,,,,,, ,不正确例, /* 没有变量x、函数func的声明 */ int x; int func(int in) { ,,,,,, C语言中,在使用变量前,一定要声明或定义。函数没有声明和定义也可以使用。但是为了保持声明和定 义的整合性,推荐把声明写在Header file,然后include Header file。 note 在学习用:语言写程序时,会被告知「不能随便使用外部变量」。 被多个函数共用的变量,通过set:、get:这些函数进入比较好。 /* 例,使用外部变量时 */ now_status = idle; ,,,,,, switch(now_status){ case idle: /* 例,不使用外部变量时 */ set_now_status(idle); ,,,,,, switch(get_now_status()){ case idle: 每个外部变量中都必须准备set和get2个函数,这样虽然很麻烦,但可以在函数内进行Error check。 另外,在程序规格变更时,对程序理解不足或技术能力不强的工程师进行修改时,,可以防止设定不使用 的变量。但是在要求动作速度的函数必须注意。 54/72 54 M4.4.4 外部变量不在多处进行定义。 参考规则 MISRA-C 8.9 相关规则 无 ,正确例, int x; /* 1个外部变量1个定义 */ ,不正确例, /* 没有变量x,函数func的声明 */ int x; long a,b,c; char k; int x; /* 在多处定义外部变量,编译不会出现错误提示 */ 外部变量可以记述多个没有初始化的定义。但是,在多个file中进行初始化时,不能保证其动作。 note 外部变量名设定时,要注意不要弄成多重定义。在上述不正确的例中,针对变量x的设定只能有一个, 所以,它的本意可能是要定义一个跟最初的变量x不同的另一个变量x,但是这样做得不到预期效果, 会变成bug。 55/72 55 M4.4.5 Header file中,不进行变量定义或函数定义。 参考规则 MISRA-C 8.5 相关规则 无 ,正确例, ---- file1.h ---- extern int x; /* 变量声明 */ int func(void); /* 函数声明 */ ---- file1.c ---- #include “file1.h” int x; /* 变量定义 */ int func(void); /* 函数定义 */ ,不正确例, ---- file1.h ---- int x; /* 外部变量定义 */ static int func(void); /* 函数定义 */ { ,,,,,,, } Header file可能会被读入到多个 Source file中。因此,如果在Header file中写变量定义或函数定义的话, 编译后生成的Object代码的size可能会变大,可能会Null。所以基本上Header file中只写声明或类型的 定义等。 note 程序编译时,编译前会进行Preprocess,前处理,。用于进行Preprocess的内部运行的程序叫 Preprocesser。 Preprocesser在build程序时,编译器会先解释执行源代码内”#”开始的「Preprocesser命令」。 #include,#define,#ifdef,#endif,#else,#if,#ifndef????, 并删除Comment。 56/72 56 M4.4.6 Header file要做得经得起重复读取。<<规定用于这个的编写方法。>> 参考规则 Indian Hill 2.3 相关规则 无 ,正确例, ---- myheader.h ---- #ifndef MYHEADER_H /* MYHEADER_H为myheader.h的Header file名 */ #define MYHEADER_H Header file的内容 #endif /* MYHEADER_H */ ,不正确例, ---- myheader.h ---- void func(void); /* end of file */ 首先必须要进行整理,尽量减少反复读取Header file。但是,还是会发生重复读取的,这时,就必须要制 作出能经得起反复读取的file。 规则示例 在Header file的开头,写判断Header读取是否完成的#ifndef宏,第二次读取中,之后的内容不是编译 対象。这时的宏名,由Header file名字大写版变成Period_,下划线,的名字。 note Header file就是在进行编译前,C Preprocesser从其它file读入的file, 像stdio.h 这样的,由几个System level定义,被所有使用标准输入输出库的程序读取。Header file必须按功能区分,也就是说,不同的 Sub System的声明应该写到其他的Header file。另外,可能会移植到其他机种的代码的声明,应该另作 一个[不依赖于机种]的另外的Header file。 声明函数或外部变量的header file,应该包括定义这个函数或变量的file。这样,编译器就可以经常检 查类型,外部声明和定义就不会经常矛盾了。 Header file不能Nest。因此,Header file的Prologue中,为了让这个Header工作,应该给它必须要 #include 的file。如果几个Source file必须要Include多个相同Header,在这种极端的情况下,可以把共 用的 #include 总结到一个include file中。,Indian Hill提取, 57/72 57 维护性4.5 统一声明的写法。 维护性4.5 统一声明的写法。 M4.5.1 函数Prototype声明中,要给所以参数命名。而且,参数和名字、及返回类型要和 函数定义、常量一样。 参考规则 MISRA-C 16.3 Indian Hill 4 相关规则 M1.4.1 ,正确例, int func1(int x, int y); int func2(float x, int Y); int func1(int x, int y) { /* 函数处理 */ } int func2(float x, int Y) { /* 函数处理 */ } ,不正确例, int func1(int x, int y); int func2(float x, int Y); int func1(int y, int x) /* 参数的名字和Prototype声明不一样 */ { /* 函数处理 */ } typedef int INT; int func2(float x, INT y) /* y的类型不是Prototype和常量 */ { /* 函数处理 */ } 函数Prototype声明中,可以省略参数的名字,适当记述的参数名,有函数界面信息的价值。写参数名时 要注意避免使用和定义相同的名字。类型也是,做成跟函数定义和常量一样的话会比较容易理解,所以推 荐做成一样的。 58/72 58 M4.5.2 构造体tab的声明和变量声明各自进行。 参考规则 无 相关规则 无 ,正确例, struct TAG { int mem1; int mem2; } ; struct TAG x; ,不正确例, struct TAG { int mem1; int mem2; } x; M4.5.3 构造体?共用体?队列的初始值表达式的List、及枚举子List最后的”}”前,不写”,”。 参考规则 无 相关规则 M2.1.1 ,正确例, struct tag data[] = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } /* 最后的要素中没有逗号 */ }; ,不正确例, struct tag x = { 1, 2, }; /* 搞不清Member有2个还是3个以上 */ 在初始化多个数据时,为了明示初始化的最后,分为了两个流派,一个是在最后的初始值后不写逗号的流派,另一个是考虑到追加、削除初始值的方便性,在最后写逗号的流派。请检讨使用哪一个,并制定规则。 另外,在C90规格中、用于表示枚举List的最后的”}”前不允许写逗号,但是C99规格中是允许的。 note C99是对C90,或ANSI-C,进行了功能扩张的,for语句内的变量声明、由变量指定size的队列、Scope中途的变量声明、//后的Comment 、In-line函数、函数中的函数定义等,。因此,按C90规格写程序会比较安全。另外,尽可能的不要使用编译器的特別规格。 59/72 59 维护性4.6 统一Null指针的书写方法。 维护性4.6 统一Null指针的书写方法。 M4.6.1 Null指针中使用NULL。NULL不使用在Null指针以外的地方。 参考规则 无 相关规则 无 ,正确例, char *p; int dat[10]; p = NULL; int[0] = 0; ,不正确例, char *p; int dat[10]; p = 0; arr[0] = NULL; 用NULL的Null指针一直以来的表现是,在不同的执行环境中表现也不一样。因此,有人认为使用0更安全。 note NULL是指 指针在某个地方持有实际的信息。也就是说,指针指向的是实体。如果想表示这个指针现在没有指向任何地方的话,就会使用NULL。即,NULL表示没有指向任何实体。 NULL和:的关系 指针的初始化或代入时,右辺即为指针,如果,它是0的话,编译器就会把它解释为NULL指针。也即是说,:可以替换NULL。但是,要注意的是,在交出函数的参数时,在必须交出指针的地方写:,并不表示它是NULL指针,而是被解释为int型的:,这时,会出现编译错误。因此,代入及比较演算时可以把0作为Null用,但作为函数的参数时,就不能使用:了。 60/72 60 维护性4.7 统一前处理指令的书写方式。 维护性4.7 统一前处理指令的书写方式。 M4.7.1 含有Operator的宏,要用括号括住宏本体和宏参数。 参考规则 无 相关规则 无 ,正确例, #define M_SAMPLE(a , b) ((a) + (b)) ,不正确例, #define M_SAMPLE(a , b) a + b 如果没有用括号括住宏本体、宏参数的话,宏展开后就可能会因为相邻的Operator和宏内Operator的优先 顺序关系,得不到想要的演算顺序,变成Bug。 note 不好的例, #define DOUBLE(x) x + x ??????? int x; x = DOUBLE(4) * DOUBLE(6); 和程序组合时,会变成4 + 4 * 6 + 6 ,优先顺序会改变, x 变成34。 不好的例, #define square(x) (x * x) ??????? int x; x = square(3+4); 答案不是49而是变成了19。 因为square(3+4)变成了(3+4*3+4)。 61/72 61 M4.7.2 把#ifdef、#ifndef、#if对应的 #else、#endif写在同一file中,并插入Comment , 明确其对应关系。 参考规则 Indian Hill 17.1 GNU 相关规则 M1.1.1 M1.1.2 ,正确例, #ifdef AAA /* AAA被定义时的理 */ ,,,,,,, #else /* AAA */ /* AAA被定义时的理 */ ,,,,,,, #endif /* AAA */ ,不正确例, #ifdef AAA /* AAA被定义时的理 */ ,,,,,,, #else /* AAA没被定义时的理 */ ,,,,,,, #endif 分开 #ifdef等依赖于宏的处理时,如果#else或#endif写的很远,中间还插入很多嵌套条件编译的话,就会 搞不清他们的对应关系。所以要在 #ifdef等对应的#else、#endif等中插入Comment写清楚。 note 62/72 62 M4.7.3 用#if调查宏名是否被定义时,可以通过define,宏名,调查。 不要写成 #if宏名。 参考规则 无 相关规则 无 ,正确例, #if define(AAA) ,,,,,,, #endif /* AAA */ ,不正确例, #if AAA ,,,,,,, #endif /* AAA */ 就算有 #if宏名,也不能就此判断宏被定义了。不止是宏没定义时,宏的值为0使也会判定为假。在检查 宏是否被定义时,应该使用define。 M4.7.4 #if、#elif中使用的definedOperator,只能写成defined(宏名)或defined 宏名。 参考规则 MISRA-C 19.14 相关规则 无 ,正确例, #if define(AAA) ,,,,,,, #endif /* AAA */ ,不正确例, #define DD(x) defined(x) #if DD(AAA) ,,,,,,, #endif /* AAA */ 言规格中,如果写成defined(宏名)或defined 宏名以外的,就等于没有定义进行怎样的处理(未定义)。 C语 有的编译器会出现错误,有的则自己解释,所以不要使用。 note definedOperator后面接着的宏名如果被定义了,其结果就为真。而且组合 !Operator可以代替 #ifndef。 63/72 63 M4.7.5 宏不能在Block内 #define、或 #undef。 参考规则 MISRA-C 19.5 相关规则 M4.7.6 ,正确例, #define AAA 0 #define BBB 1 #define CCC 2 struct stag { int mem1; char *mem2; }; ,不正确例, /* 有的Memory会限制被设定的值的。 */ struct stag { int mem1; /* 以下值可以设定 */ #define AAA 0 #define BBB 1 #define CCC 2 char *mem2; }; 宏定义(#define)一般的总结在file的开头。如果分散写在Block内就可能降低可读性。另外,在Block内解除 定义(#undef)也会降低可读性。宏定义跟变量不同,有効范围直到file结尾。不正确例的程序可以变更为下 例所示。 enum etag { AAA, BBB, CCC }; struct stag { enum etag mem1; char *mem2; }; M4.7.6 不能使用#undef。 参考规则 MISRA-C 19.6 相关规则 M4.7.5 #define的宏名可能会因为#undef变成没有定义的状态,参照宏名的地方可能出现解释不同的危险,会降 低可读性。 64/72 64 维护性5 维护性5 编写容易测试的代码。 嵌入式软件开发中,不可欠缺的工作之一就是动作确认,测试,。但是,近年复杂的嵌入 式软件中,有时会出现一些测试时检测不到的问题,或错误现象不再重现这样的问题。 因此,在编写代码时,希望考虑到问题分析,制作易分析问题原因的代码。特别是利用 动态Memory等,有内存泄漏的危险,处理时要特别小心 「维护性5 」由以下两个习惯用法构成。 维护性,., 采用出现问题时比较容易调查原因的编码方法。 采用出现问题时比较容易调查原因的编码方法 维护性,., 在分配、使用动态Memory时注意。 在分配、使用动态Memory时注意 65/72 65 维护性5.1 <<采用出现问题时比较容易调查原因的编码方法。>> 维护性5.1 <<采用出现问题时比较容易调查原因的编码方法。>> M5.1.1 <<规定:设定Debug Option时调试用的编码方法和、在Release Module中保存 Log的编码方法。>> 参考规则 无 相关规则 无 ,?在Debug处理的切分处使用宏定义。 调试用的代码用 #ifdef DEBUG分开(在编译时指定DEBUG宏)。 调试用程序在debug_macros.h中定义、使用时,包括其Header。 调试用的程序如下调用、使用宏。 --- debug_macro.h --- #ifdef DEBUG #define DEBUG_PRINT(str) fputs(str, stderr) #else #define DEBUG_PRINT(str) ( (void) 0 ) /* no action */ #endif /* DEBUG */ [代码例] void func(void) { DEBUG_PRINT(“>> func\n”); ??????? DEBUG_PRINT(“>> func\n”); ,? 使用assert。 C语言规格中,准备assert宏作为程序診断功能。 知道契约Programing(DBC:Design By Contract)吧。程序的代码上,用于确认实否满足所有该满足的条 件,有没有违反的。最基本的就是有assert。就像你们所知道的,检查条件,如果不一致就输出Log, 进行abort。 #include #include #define ENTRY_MAX 5 int g_entry[ENTRY_MAX] intnth_entry(int n) { assert(n < ENTRY_MAX && n >= 0) return g_entry[n]; } 上述代码中,n被5以上调用的话,就会abort、停止程序。如果在监测到不对时就马上停止程序, 用调试器使它运行的话,就可以取得bug trace。( 给assert() 的条件式不要忘了「在假的时候停止」。) 如果没有assert的话,会怎么样呢,如果运气好的话,会因为进入不正确的内存,导致 segmentation fault停止或什么,和使用assert时一样,这样也许就能发现发生地点。但是,大多数时候返回不定 值后,会再继续执行一下,这时的Bug已经表面化,很难找到到底是哪里有Bug。要抓住发生Bug 66/72 66 的瞬间非常麻烦,所以用assert在发现异常的瞬间使它停止是非常有効的手段。 偶尔我们还能看到以下这样的代码。 #include #include #define ENTRY_MAX 5 int g_entry[ENTRY_MAX] int nth_entry(int n) { if (n >= ENTRY_MAX || n < 0) return -1; /* out of range */ return g_entry[n]; } 进入队列范围外时,会返回Error(-1)。如果是外部提供的函数,也可能会这样,但如果这是内部函数的话,返回Error没有意义。内部逻辑不正确时,希望不要返回错误,而是在异常检测中abort。在根本不进行Error check的函数中返回Error没有任何意义。应该像前面那样写入assert代码。 assert非常有用。有必须要满足的条件时,应该勤勤恳恳的写入assert。不只是函数的参数、复杂的十进制计算过程中也能使用。Assert和Comment 不同,因为直接连接到代码上,所以不会和代码不一致。 但是,写入assert后,会对这部分的检查、代码Size、处理时间有影响。所以,数量很多时,最好是可以分别将各个阶段的assert设置为OFF。 #define DEBUG_LEVEL 0 #if DEBUG_LEVEL > 0 #define ASSERT_1(x) assert(x) #else #define ASSERT_1(x) #endif #if DEBUG_LEVEL > 1 #define ASSERT_2(x) assert(x) #else #define ASSERT_2(x) #endif ,? Release后的log输出 在没有Debug代码的Release的模块中,加入调查问题用的内容也是有用的。常用的方法是,把调查信息作为log留下来。在确认Release 模块的妥当性的测试中,以及给客户提供的系统发生的问题调查中,Log信息都能起作用。 留下log信息时,确定以下项目,并作为规则。,希望在进行编码前決定, ? Timing 留下Log不止是在发现异常状态的时候,在跟外部系统通信时等,要了解异常状态发生的原因、追加履历时也要设定。 ?Log中留下的信息 67/72 67 之前执行的处理、数据值、Memory的trace信息等,了解异常状态发生的原因时需要的历史数据。 ? 用于信息输出的宏、或函数 Log信息输出会作为宏或函数局所化。因为,Log输出后能变更的会好很多。 68/72 68 M5.1.2 在一个宏中,不可以多次使用#或##的前处理Operator。 参考规则 MISRA-C 19.12 相关规则 无 ,正确例, #define AAA(a, b) a#b #define BBB(x, y) x##y ,不正确例, #define AAA(a, b, c) a#b##c #Operator和##Operator的评估顺序没有被规定,但不可以混合#Operator和##Operator或使用2次以上。 note #Operator是把宏函数的参数变换为有引用符号的文字列。##Operator是连接2个标识符。 ?#Operator #Operator在声明宏时使用。有把宏的参数变为文字列的功能。例如、 #define MACRO(s) # s 把给宏的参数置换成文字列。如果如下使用这个宏的话、 int num = 10; puts( MACRO(num) ); puts函数出力的就是"num"的文字列。宏置换的结果就会明显易懂。 puts( "num" ); 这个不常使用,是能输出变量名的重点。 ?##Operator ##Operator用于连接,个Source上的文字。这个也是在宏声明时使用。例如、 #define MACRO(s) (s ## 100) 这时,在给宏的参数后连接「100」这样的文字。如果如下使用这个宏的话 int num = 123; int num100 = 456; printf( "%d\n", MACRO(num) ); 被出力的不是变量num的值,而是变量num100。看看宏置换的结果, printf( "%d\n", num100 ); 69/72 69 M5.1.3 使用函数好过函数形式的宏。 参考规则 MISRA-C 19.7 相关规则 E1.1.1 ,正确例, int func(int arg1, int arg2) { return arg1 + arg2; } ,不正确例, #define func(arg1, arg2) (arg1 + arg2) 使用函数代替函数形式的宏的话,Debug时,在函数开始时停止等处理比较好进行。 另外可以通过编译器来检查类型,容易找出编码失误。 70/72 70 维护性5.2 在分配、使用动态Memory时注意 维护性5.2 在分配、使用动态Memory时注意 M5.2.1 不使用动态Memory。 参考规则 MISRA-C 20.4 相关规则 无 如果使用动态内存的话,可能会因为错误进入内存、或忘了归还内存给系统,引起资源耗尽,导致内存泄漏。如果已经隐藏了这些问题了的话,为了容易调试,事先规定一些测试用的代码制作的规则会方便些。 note 动态Memory使用时的问题点 ,?Buffer Over Flow 超过获得的内存范围进行参照或更新。特别是更新范围外时,并不是更新的地方发生故障,而是参照 更新时破坏掉的内存的地方发生故障。麻烦的是,如果是动态内存的话,很难找到是哪一处内存被破坏 了。 2?初始化遗漏 通常的动态内存函数获得的内存不被初始化,一部分动态内存获得的函数中会初始化,。和自动变量一 样,程序中必须初始化后再使用。 ,?Memory Leak 忘记归还内存。在1次1次结束的程序中没问题,但在连续运行的程序中,如果内存泄漏的话,会发生 内存枯竭、系统异常的危险。 ,?返回后的使用 free函数等归还内存的话,这个内存之后还可能被malloc函数等再次利用。因此,使用free后的内存地 址更新时,会破坏作为其他用途使用的内存。就像Buffer over flow中说明的一样,这个问题非常难调试。 有着这些问题的代码不会发生编译错误。而且,不会在隐藏问题的地方出现故障,所以在确认一般规格的 测试中发现不了问题。不进行代码审核、插入用于发现这些问题的测试代码、嵌入特别的库进行测试的 话,就不能Debug。 71/72 71 维护性6 维护性6 72/72 72
/
本文档为【编码规则(可维护性)】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索