引 言
嵌入式系统是指以应用为中心,以计算机技术为基础,软硬件可裁剪,适应应用系统对功能、可靠性、成本、体积和功耗严格要求的专门计算机系统。嵌入式技术并不是一个独立的学科,它是伴随着微电子技术和计算机技术的发展,微控制芯片功能越来越强大,而嵌入微控制芯片的设备和系统越来越多而发展起来的。嵌入式系统几乎包括了生活中所有的电器设备,如:mp3、手机、数字电视、汽车、微波炉、数码相机、电梯、空调、自动售货机、工业自动化仪表与医疗仪器等。
尽管C语言是嵌入式软件开发的主要工具,然而C语言并非是专门为嵌入式系统设计的,一是因为标准C语言编写的软件缺乏安全性;二是因为“标准C语言”太庞大了,很难操作。因此,需要制订针对嵌入式系统软件的编码格式要求。
本标准是在理解标准C语言的基础上,结合嵌入式软件的开发实践以及嵌入式软件开发中常见的危险编码方式制订的,着重于软件的安全性、可读性。既可作为嵌入式软件开发中的编码要求,也可作为软件交付时验收方的验证要求。
在要求条款中,“应”标示的要求是必须符合的,“宜”标示的要求是可选择符合的。
嵌入式软件 C语言编码规范
1 范围
本标准规定了使用C语言编写嵌入式软件的编码格式要求。本标准也提及了软件设计人员应该注意的与编码相关的事项。
本标准适用于嵌入式软件生存周期的编码阶段,主要供具有C语言编程能力的软件编码人员使用。
2 规范性引用文件
下列文件对于本文件的应用是必不可少的。凡是注日期的引用文件,仅所注日期的版本适用于本文件。凡是不注日期的引用文件,其最新版本(包括所有的修改单)适用于本文件。
GB/T 15272 程序设计语言 C
3 术语和定义
GB/T 15272中界定的以及下列术语和定义适用于本文件。
3.1
单边作用 side effect
指在表达式执行后对程序运行环境可能会造成影响。赋值语句、自增等操作都是典型的具有单边作用的操作。
4 编码格式要求
4.1 总体要求
4.1.1 编程前应阅读硬件电路和芯片资料――熟悉芯片的各种内存、寄存器地址、中断服务、定时器、通讯接口等功能,必要时将相关信息加入程序注释中。
4.1.2 编程中应注意程序的存储空间――如指令空间大小、数据空间大小、堆栈空间大小等是否超出系统有关限制。
4.1.3 使用联合体时,应明确该编译器联合体存储的细节――如联合体的末尾有多少个填充单位、联合体的各个成员如何对齐、多字节的数据类型高低字节如何排放顺序等。
4.1.4 宜注意硬件系统复位和软件复位的方法和区别――在程序中宜使用芯片提供的看门狗功能实现硬件系统复位。
4.1.5 宜注意CPU对各种存储器的访问速度――在程序中宜利用各种硬件设备自身的特点来提高程序效率。
4.1.6 应注意所使用的编译器的位数,支持的C语言标准,调试程序所占用的内存,兼容性等特点。
4.1.7 应注意编译器处理不同数据类型的原则及有关细节――如static局部变量将在内存数据区中生成,而非static局部变量将在堆栈中生成。
4.1.8 应注意程序开发调试环境和实际应用环境的区别。
4.1.9 应用程序宜使用操作系统驱动程序来调用硬件端口。
4.2 内存空间管理类
4.2.1 在使用malloc等其它函数获取内存时,应判断申请内存是否成功。
4.2.2 动态内存的申请与释放应配对,防止内存泄漏。
应用场景主要包括:
a) 过程/函数中分配的内存,在过程/函数退出之前要释放;
b) 过程/函数中申请的(为打开文件而使用的)文件句柄,在过程/函数退出之前要关闭。
错误用法示例:
int example_fun( BYTE gt_len, BYTE *gt_code ) { BYTE *gt_buf; gt_buf = (BYTE *) malloc (MAX_GT_LENGTH); ...
/* global title length error */ if (gt_len > MAX_GT_LENGTH) { return GT_LENGTH_ERROR; //退出之前没有释放gt_buf }
... // other program code } |
正确用法示例:
int example_fun( BYTE gt_len, BYTE *gt_code ) { BYTE *gt_buf; gt_buf = (BYTE * ) malloc ( MAX_GT_LENGTH ); ...
/* global title length error */ if (gt_len > MAX_GT_LENGTH) { free( gt_buf ); // 退出之前释放gt_buf return GT_LENGTH_ERROR; }
... // other program code } |
4.2.3 不应引用已经释放的内存空间。
4.2.4 应防止内存操作越界。
应防止越界操作数组、指针、内存地址等内存空间。
4.2.5 字符串连接宜使用strncat库函数代替strcat库函数,字符串拷贝宜使用strncpy库函数代替strcpy库函数,避免长度不够引起的数组越界。
4.2.6 使用sprintf库函数时,应注意字符长度,避免长度不够引起的数组越界。
4.2.7 对于内存受限的系统,宜少用动态内存分配,尽量选用数组。
4.2.8 对于内存受限的系统,在分配内存时,应考虑内存碎片的问题。
4.3 中断处理类
4.3.1 中断服务程序不应有返回值。
4.3.2 中断服务程序中不应使用printf()函数。
4.3.3 对于中断中使用到的非局部变量,在中断处理函数中应对其进行入栈保护。
4.3.4 中断处理程序中的变量,如果会被其他函数执行读操作或者写操作,那么在其他函数读写这个变量前,应先关中断,读写完,再开中断。
4.3.5 对于开关中断,要注意成对匹配。
对于默认开启的中断,如果在某个函数中进行了关闭,在函数退出时需进行相应的开启。
对于默认关闭的中断,如果在某个函数中进行了开启,在函数退出时需进行相应的关闭。
4.3.6 宜避免在中断服务程序中进行浮点数运算。
4.4 系统接口类
4.4.1 不应随意更改其它模块或系统的有关设置和配置。
4.4.2 不应随意改变与其它模块的接口。
4.4.3 应充分了解系统的接口之后,再使用系统提供的功能。
4.5 硬件系统初始化类
4.5.1 系统运行之初,应初始化有关变量及运行环境。
4.5.2 系统运行之初,应对加载到系统中的数据进行一致性检查。
4.5.3 在硬件系统初始化之前,宜判断工作电压是否已经稳定。
4.6 软件模块初始化类
4.6.1 所有变量在使用之前应被初始化。
错误用法示例:
unsigned int x; unsigned int y; y=x; /* x 没有初始值 */ |
4.6.2 对局部声明不应用 extern 初始化。
错误用法示例:
int fn(void) { extern int x = 0; return(x); } |
4.6.3 数组、结构和联合的初始化列表应显式描述。
数组、结构和联合的初始化列表应使用大括号,并使用附加的大括号来指示嵌套的结构;程序员应显式地考虑和描述复杂数据类型的所有元素,不应忽略某个元素的初始化。
错误用法示例:
int16_t y[3][2] = { 1, 2, 3, 4, 5, 6 }; int16_t y[3][2] = { {1, 2} }; |
正确用法示例:
int16_t y[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; |
4.6.4 枚举元素的初始化应完整。
枚举元素的初始化只有两种形式是安全的,一是初始化所有元素,二是只初始化第一个元素。
错误用法示例:
enum E_type{num1, num2 = 2, num3}; |
4.7 版面书写类
4.7.1 文件注释应采用统一格式。
文件注释示例:
/* * Copyright (c) 2007, 公司名称 * All rights reserved. * 文件名称: filename.c * 摘 要: 简要描述本文件的内容 * 当前版本: x.x, 编写者/修改者,修改时间,修改内容 * 历史版本: x.x, 编写者/修改者,修改时间,修改内容 */ |
4.7.2 函数注释应采用统一格式。
函数注释示例:
/* * 函数介绍: * 参数: * 返回值: * 备注: */ |
4.7.3 不应使用嵌套的注释。
4.7.4 应保证注释与代码的一致性,无用的注释应删除。
4.7.5 对单条语句代码的注释应放在其上方或右方相邻位置。
4.7.6 对于有物理含义的变量、常量、数据结构(包括数组、结构、类和枚举等),如果其命名不是充分自注释的,在声明时应加以注释,说明其物理含义。
4.7.7 控制语句应用大括号括起来。
错误用法示例:
if (a == 1) x = y;
while ((c = getchar( )) != EOF) putchar(c);
for ( ; ; ) timetest(n); |
正确用法示例:
if (a == 1) { x = y; }
while ((c = getchar( )) != EOF) { putchar(c); }
for ( ; ; ) { timetest(n); } |
4.7.8 注释与其上面的代码应用空行隔开。
4.7.9 宏定义应大写。
正确用法示例:
#define MAX 100 #define PI 3.14159 |
4.7.10 函数风格的宏应用括号“()”括起来。
错误用法示例:
#define ADD(a, b) a + b |
正确用法示例:
#define ADD(a, b) ((a) + (b)) |
4.7.11 代码中的一行中只应有一个声明或者一条语句。
错误用法示例:
int i; int j; /*多个声明*/ j = i; i++; /*多条语句*/ |
4.7.12 超过120个字符的长语句,应分成多行书写。
4.7.13 一个文件中的程序总行不宜超过2000行。
4.7.14 一个函数中的程序总行不宜超过200行。
4.7.15 宜用括号“()”明确表达式的操作顺序,避免使用默认优先级。
4.7.16 宜避免使用逗号操作符。
4.8 声明定义类
4.8.1 头文件中不应有对象或函数的定义。
头文件中只存放“声明”而不存放“定义”。
4.8.2 不应单独使用小写字母“l”或大写字母“O”作为变量名。
小写字母“l”很容易与数字“1”混淆,大写字母“O”很容易与数字“0”混淆。
4.8.3 函数参数不应只有类型名没有标识符。
4.8.4 使用八进制数应加以注释。
八进制数是以0开始,易与十进制数混淆。
4.8.5 局部变量不应与全局变量重名。
4.8.6 不应使用未知大小的数组。
当声明一个数组时,其大小应该显式声明或者通过初始化进行隐式定义。
4.8.7 不应重新定义使用C的关键字。
4.8.8 non-void类型函数的所有出口路径都应该有一个明确的return语句表达式,而不应该只有return。
错误用法示例:
int func(void) { … return; } |
4.8.9 在函数参数中不应使用static存储类标识符。
错误用法示例:
int func(static int x); |
4.8.10 类函数宏调用时不应没有它的参数,并且参数不应多于或少于宏定义时的参数。
错误用法示例:
# define ABC(x) ( (x) + 3 ) b = ABC;
#define MAX(a, b) ((a) > (b) ? (a) : (b)) long maxnum = MAX(intnum); |
4.8.11 字符型变量应明确定义是有符号的还是无符号的。
4.8.12 在同一嵌入式软件产品内,应规划好接口部分标识符(变量、结构、函数及常量)的命名,防止编译、链接时产生冲突。
例如:可规定接口部分的变量与常量之前加上“模块”标识等。
4.8.13 在文件范围内声明和定义的所有对象或函数应具有内部链接,除非是在需要外部链接的情况下。
如果一个变量只是被同一文件中的函数所使用,那么就用static。类似地,如果一个函数只是在同一文件中的其他地方调用,那么就用static。使用static存储类标识符将确保标识符只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。
4.8.14 用typedef自定义的类型不应被重新定义。
改变用户自定义的类型会引起混淆甚至能导致错误,因此用typedef自定义的类型不应被重新定义。
4.8.15 预处理语句“#if”和“#endif”不应分散在不同的文件之中。
4.8.16 被包含文件中的“#else”、“#elseif”、“#endif”不应与父文件中的“#if”匹配。
4.8.17 “#if”表达式中使用的宏应该已经被定义,并且“#if”表达式只能包含整型常量。
错误用法示例:
#define BIGINT 32767 #define BIGREAL 1e38 #if (BIGREAL > BIGINT) /* BIGREAL是浮点型常数 */ ... #endif |
4.8.18 应避免一个头文件的内容被包含两次。
4.8.19 宏如果需要被重定义,应该先用“#undef”解除前面的定义。
4.8.20 除了编译开关/头文件等特殊应用,不应使用“_EXAMPLE_TEST_”之类以下划线开始和结尾的定义。
4.8.21 限定符static不应用于定义标记。
错误用法示例:
static struct ST1 { int a; int b }; |
4.8.22 地址操作符“&”不应用于一个声明为“register”类型的对象。
错误用法示例:
static void foo (void) { register int a; int *pa; pa = &a; /* “&”不应用于一个声明为“register”类型的对象 */ } register int array[10]; int * ptr = &array[0]; /* “&”不应用于一个声明为“register”类型的对象*/ |
4.8.23 struct或union类型中至少应有一个成员。
4.8.24 对struct或union成员不应使用存储类指示符。
错误用法示例:
struct record { extern int field; }; |
4.8.25 struct或union类型的成员不应是void类型、函数类型、未知大小的数组和含有未知内容的struct或union。
4.8.26 宜使用“typedef”在头文件中对基本类型定义。
正确用法示例:
typedef char char_t; typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef signed long int64_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef unsigned long uint64_t; typedef float float32_t; typedef double float64_t; typedef long double float128_t; |
4.8.27 对只设置一次的变量,宜使用“const”限定词进行声明。
4.8.28 宜优先考虑使用函数而非函数式宏定义,在宏参数中不宜包含自增、自减或赋值等带有单边作用的操作符,宏参数中也不宜包含函数调用。
4.8.29 函数中不宜使用过多的参数,建议不超过20个。
4.8.30 不宜在一个函数块中单独使用“#define”或“#undef”。
4.8.31 应谨慎使用“#pragma”。
4.9 控制语句类
4.9.1 条件判别成立时相应分支不应无执行语句。
4.9.2 在if…else if语句中应使用else分支。
4.9.3 在switch语句中应有default语句。
4.9.4 不应使用空switch语句。
4.9.5 switch语句中不应只包含default语句。
4.9.6 所有非空的switch case子句都应以break 语句结束。
4.9.7 switch控制表达式不应是一个常量或逻辑表达式。
逻辑表达式只有真和假两种情况,当需要对逻辑表达式判别时,应使用if …… else语句而不是switch语句。
4.9.8 每一个switch语句都应至少有一个case子句。
在switch的case或default之前不应存在声明和语句。
4.9.9 case表达式的类型应与switch控制表达式的类型一致。
4.9.10 switch的子句不应位于一个嵌套的代码块中。
错误用法示例:
int foo(int i, int p) { int ret = p;
switch (i) { case 0: if (ret > 2) { ret++; break; case 1: ret = 2; break; case 2: ret = 3; break; } default: ret = 4; break; } } |
4.9.11 case表达式的大小不应超过switch控制表达式的大小。
4.9.12 循环控制变量的类型应为整型。
浮点数不应用作循环控制变量,在循环执行时会得到不可预料的结果。
错误用法示例:
… for (ft = 0.0F; ft < 10.0F; ft = ft + 2.0F) ++loop; … |
4.9.13 循环控制变量应为局部变量。
循环变量应在在最小的范围内定义,以免变量被外部修改导致循环不可控制,即循环变量的作用域应最小。
4.9.14 for循环控制语句中的3 个表达式只应和循环控制相关。
第一个表达式只能为循环变量赋初值,第二个表达式只能进行循环条件的判断,第三个表达式只能进行循环变量增(减) 值这样可以增强程序的可理解性。
错误用法示例:
for (i == 0; i < 10; i++) /*第一个表达式不是赋值表达式*/ { …… } |
4.9.15 for 循环中,循环变量只应在for循环控制语句的第三个表达式中修改,不应在循环体中修改。
4.9.16 不应直接从函数中跳出。
直接从函数中跳出破坏了程序的结构化。
错误用法示例:
#include <setjmp.h>
void foo(jmp_buf mark, unsigned int val) { … longjmp(mark, val); } |
4.9.17 不应使用 goto 语句及标号。
4.9.18 不宜在循环中使用break语句。
4.9.19 for循环不宜只执行一次。
4.10 类型转换类
4.10.1 应减少没有必要的数据类型默认转换与强制转换。
对编译系统默认的数据类型转换,应有充分的认识,默认的数据类型转换有可能损失数据的精度,可能改变数据的符号属性。表达式如果是函数的参数或函数的返回表达式,不能出现默认数据类型转换。当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。常数被默认转换为无符号数时,应充分考虑到无符号变量的空间大小。
4.10.2 应禁止 signed 类型与 unsigned 类型之间的隐式转化。
从有符号类型转换为无符号类型会导致符号的丢失。无符号数转换为有符号数,可能得到一个负值。
4.10.3 应禁止 int 类型与 float 类型的隐式转化。
浮点类型与整型之间的相互转换为会导致精度的丢失。
错误用法示例:
double d64_a; float f32_a; signed int i16_a; ... d64_a = i16_a; /* 整型数转换为浮点数 */ si16_a = f32_a; /* 浮点数转换为整型数 */ ... |
4.10.4 不应对指针变量使用强制类型转换赋值。
错误用法示例:
void foo(void) { unsigned short s = 0; unsigned int *p1_ptr; p1_prt = (unsigned int*)s; … } |
4.11 指针、数组使用类
4.11.1 不应把自动类型的局部变量的地址赋值给外部指针。
局部变量的地址赋值给外部或者范围更大的指针,如果局部变量不是静态的,那么这种用法是不安全的。
错误用法示例:
extern int* pi;
void f(void) { int a; pi = &a; } |
4.11.2 指针的指针不应超过两级。
4.11.3 指向不同数据类型的指针之间不应相互转换。
不同的数据类型分配的字节空间可能不同,将指向多字节空间的指针转换为指向少字节空间的指针不会有问题,反之有可能出现错误。
4.11.4 指针转换过程中不应丢失指针的const、 volatile属性。
示例:
int x; int *pi; /* 指向整形的指针 */ int *const pci1 = &x; /* const指针 */ const int *pci2; /* 指向const整形的指针 */ volatile int *pci3; /* 指向volatile整形的指针 */ ... pi = pci1; /* 符合本条要求 */ pi = (int *)pci2; /* 不符合本条要求 */ pi = (int *)pci3; /* 不符合本条要求 */ |
4.11.5 只有指向数组的指针才允许进行算术运算。
注:此处的算术运算仅仅限定于指针加减某个整数,比如ppoint = point -5, ppoint++等。
错误用法示例:
int *px; int i; px = &i; px = px + 2; ++px; |
正确用法示例:
int *px; int a[10]; px = a; px = px + 2; ++px; |
4.11.6 只有指向同一数组的两个指针才允许相减。
注:此处两个指针可以指向同一数组的不同成员。
错误用法示例:
int a[10]; int b[10]; int x, *p1, *p2; x = p1 – p2; |
4.11.7 只有指向同一数组的两个指针才允许用>、>=、<和<=等关系运算符进行比较。
注:此处两个指针可以指向同一数组的不同成员。
错误用法示例:
char a[10]; char b[10]; int x; x = a < b; |
正确用法示例:
char a[10]; char *p1, *p2; int x; p1 = a; p2 = a+2; x = p1 < p2; |
4.11.8 指针的索引值不应为负。
错误用法示例:
void foo(int *p, int n) { int r; if (n < -5) { r = p[n]; /* 指针的索引值为负 */ ... r = *(p + n); /* 指针的索引值为负 */ } } |
4.11.9 不应对指针值可能为NULL的指针进行算术运算。
错误用法示例:
extern int buf[10]; void foo( int n) { int *p = NULL; for (i = 1; i <= n; ++i) { ... p = &buf[i]; ... } ++p; /* 对指针值可能为NULL的指针进行算术运算 */ } |
4.11.10 对传递到函数的指针参数应进行是否为空判断。
4.11.11 调用返回类型为指针的函数后,应进行是否为空的判断。
4.11.12 除常量字符串外,其他字符数组应指定长度。
4.11.13 局部变量的地址不应在本对象消亡后传给另外一个对象。
本条要求分为以下几种情况:
a) 将动态分配的局部变量的地址赋值给一个带链接的或更宽范围的指针是危险的,一旦局部变量的内存空间被释放,这个指针会成为无效的指针(见示例1);
b) 函数参数不应返回动态数据的地址(见示例2);
c) 函数参数不应返回本地静态数据的地址(见示例3)。
错误用法示例:
示例1:
extern int* pi;
void f(void) { int a; pi = &a; } |
示例2:
void foo(int *appi) { int bi = 1; ... *appi = &bi; return; } |
示例3:
例3: void foo(int *appi) { static int bi = 1; ... *appi = &bi; return; } |
4.11.14 一个未知大小的对象的指针不应成为加法或者减法操作的左操作数。
错误用法示例:
typedef struct TAG T;
static int foo(int n, void *pv) { T *v1 = 0;
v1 = v1 + n; /* 不符合本条要求 */ pv = pv + n; /* 不符合本条要求 */ v1 = v1 - n; /* 不符合本条要求*/ pv = pv - n; /* 不符合本条要求 */ } |
4.11.15 数组的索引值不应越界。
4.11.16 宜减少指针的使用。
4.11.17 多线程应用中被几个任务共享的变量宜使用“volatile”。
4.12 运算处理类
4.12.1 不应对有符号数进行位运算。
4.12.2 无格式的字符型不应与负数常量和零比较。
注:无格式的字符型指既没有定义为unsigned也没有定义为signed的char类型。不同的编译器对其处理是不一样的,有的定义其可正可负,有的定义其只能为正。
错误用法示例:
void f(char c1) { if (c1 < -3) /* 和负数常量比较 */ { c1++; } if (c1 < 0) /* 和零比较 */ { c1++; } } |
4.12.3 无符号值不应与负数常量比较。
4.12.4 对变量进行移位操作应保证不会产生溢出和数据截短。
4.12.5 不应在布尔表达式中使用赋值操作符。
4.12.6 不应对布尔表达式进行算术或者位运算。
4.12.7 表达式的值在任何求值顺序下应保持一致。
注:防止同一个表达式在不同的编译器中得到的结果不一样。
错误用法示例:
x = b[i] + i++; /*不同的编译器给出的结果不一样,b[i]不一定先执行*/ x = 2; y = 3; x = y * x++; /*求值顺序无法确定*/ |
4.12.8 应避免除数可能为0的情况出现。
4.12.9 赋值运算符的左操作数不应进行转换操作,并且应该是一个可更改的对象。
错误用法示例:
int c = 0; float ff = 20.0F; float gg = 5.0F; const float f1 = 0.0F; /* 不符合本条要求 */ (char)c = ff * gg; /* 不符合本条要求 */ |
4.12.10 sizeof操作符不应用在有单边作用的表达式上。
错误用法示例:
int i; int j; j = sizeof( i = 1234 ); /* “i = 1234”没有执行 */ |
4.12.11 逻辑运算符“&&”或者“||”右边不应包含单边作用。
注:如果“&&”的左边已经为0或者“||”的左边已经为1,则右边不会执行。
错误用法示例:
if(ishight) && (x == i++)) /* 如果ishight=0,那么i++不会执行 */ |
4.12.12 逻辑运算符(&&、|| 和!) 的操作数宜为一个有效的布尔值,布尔值表达式不宜进行逻辑运算以外的操作。
4.12.13 不应对无符号性的表达式进行一元减运算。
4.12.14 不宜在同一个表达式中混合使用“+ +” 和“- -”运算符。
4.13 函数使用类
4.13.1 传递给一个函数的参数应与声明的参数匹配。
4.13.2 函数原型中的指针参数如果不是用于修改就应声明为指向 const的指针。
正确用法示例:
void myfunc ( int16_t *param1, const int16_t *param2, const int16_t *param3 ) /* param1 : 指针指向的对象被修改了 – 不需要 const 限定 param2 : 指针指向的对象没有被修改 –需要 const 限定 param3 : 指针指向的对象没有被修改 –需要const 限定 */ { *param1 = *param2 + *param3; return; } |
4.13.3 不应把 auto 类型的局部变量地址作为函数的返回值或者通过指针参数返回。
注:return 语句不可返回指向“栈内存”的“指针”或者“引用”。auto 类型的局部变量在退出函数后会被自动销毁,内存变为没有意义。
错误用法示例:
void foo(int **appi) { int bi = 1; ... *appi = &bi; /* 不符合本条要求 */ ... return; } int8_t * foobar (void) { int8_t local_auto; return (&local_auto); /* 不符合本条要求 */ } |
4.13.4 在函数的调用中,函数标识符之后应该有“()”。
注:在函数的调用中,如果函数名后面不加“()”,表示函数的指针,而不是函数的调用。
4.13.5 宜避免在函数中使用 static 局部变量。
注:尽量避免函数带有“记忆”功能,相同的输入应当产生相同的输出。带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种“记忆状态”。这样的函数既不易理解又不利于测试和维护。
4.13.6 宜避免函数参数在调用中未被使用。
4.13.7 宜谨慎使用abort,exit等函数。
注:这些函数会导致程序终止执行,有可能导致嵌入式系统无法正常运行。
4.13.8 不宜使用函数的递归调用。
注:递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
4.14 数据冗余类
4.14.1 不应存在执行不到的代码。
4.14.2 表达式语句上层不应包括多余的操作符和转换。
错误用法示例:
a == b++; /* 该表达式包含的操作符“==”没有被用到 */ (int)(a += 1); |
4.15 程序效率类
4.15.1 应保证循环体内工作量最小化。
应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。如下代码效率不高:
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) { sum += ind; back_sum = sum; /* backup sum */ } |
语句“back_sum = sum”完全可以放在for语句之后,如下:
for (ind = 0; ind < MAX_ADD_NUMBER; ind++) { sum += ind; } back_sum = sum; /* backup sum */ |
4.15.2 在多重循环中,应将循环次数多的循环放在内层。
将循环次数多的循环放在内存,可以减少CPU切入循环层的次数。
如下代码效率不高:
for (row = 0; row < 100; row++) { for (col = 0; col < 5; col++) { sum += a[row][col]; } } |
可以改为如下方式,以提高效率:
for (col = 0; col < 5; col++) { for (row = 0; row < 100; row++) { sum += a[row][col]; } } |
4.15.3 应尽量避免循环体内含判断语句。
目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外。
4.15.4 应注意switch-case语句的使用。
为了提高程序效率,应把switch-case 语句中的每一种具体情况按照它们发生的相对频率排序。即把最可能发生的情况放在第一,最不可能发生的情况放在最后。
4.15.5 应避免使用标准库函数。
很多大的库例程设法处理所有可能的情况,占用了庞大的内存空间,因此嵌入式系统编程应尽可能地减少使用标准库函数。