C语言代码规范

一、排版

1.相对独立的程序块之间、变量说明之后必须添加空行。

2.较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进,使排版整齐,语句可读。

paRam->srcDstCIdx
    = (pXferParams->cDstOffset << 16)
      | pXferParams->cSrcOffset;

3.不允许把多个短语句写在一行中,即一行只写一条语句。

rect.length = 0;
rect.width = 0;

4.if,for,do,while,case,switch,default等语句自占一行,且if,for,do,while等语句的执行语句部分无论多少都要加括号{}。

if (pUserCR == NULL)
{
    return;
}

5.在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。

a = b + c;
a *= 2;
*p = 'a';
flag = !isEmpty;
p = &men;
i++;
p->id = pid;
if (a >= b && c > d)

二、注释

1.一般情况下,源程序有效注释量必须在20%以上。
对于提供给外部使用的头文件中,其中的注释越多越好,充分讲述清楚各个数据结构和接口函数的原理与作用,详尽地说明每个成员和参数。对于维护性程序的修改,必须添加注释。

2.说明性文件(如头文件.h文件、.inc文件、.def文件、编译说明文件.cfg等)头部应进行注释。注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、修改日志等。

// psp_edma.c
//
// Copyright(C)2011-2013
//
// Author: 
// Version: V1.0.0 2012-01-10 Create
//
// Description: EDMA3驱动程序。
//         1.硬件说明
//         XXX
//         2.程序结构说明
//         XXX
//         3.使用说明
//         XXX
//         4.局限性说明
//         XXX
//         5.其他说明
//         XXX
//
// Modification:
// 1.Date:
//    Revision:
//    Author:
//    Contents:
// 

3.函数头部应进行注释。列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数、表)等。

// 函数名:PSP_edmaSetSrcAddr
// 描述:设置特定EDMA通道的源地址 保存在paRam的srcAddr成员中
// 输入:handle——EMDA3的通道句柄
// 输出:无
// 返回值:FVID2_SOK——成功
//               FVID2_EXX——失败
Int32 PSP_edmaSetSrcAddr(PSP_EdmaHandle hEdma, Uint32 srcAddr);

4.注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在函数下面,如放于上方需与上面的代码用空行隔开。

gEdmaIntNum++;

// 使用索引号作为TCC 所以查询的次数为对象的数量
for (int i=0; i<EDMA_MAX_CH_NUM; i++)
{
    // dosomething
}

5.对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释,说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。

#define MAX_ACT_TASK_NUMBER 1000 // 激活的统计任务数

6.全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明。

7.将注释与其上面的代码用空行隔开。

// 代码注释1
program code one;

// 代码注释2
program code two;

8.在代码的功能、意图层次上进行注释,提供有用的、额外的信息。
注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。

9.在程序块的结束行右方加注释标记,以表明某程序块的结束
当代码段较长,特别是多重嵌套时,这样做可以使代码更清晰,更便于阅读。

if (...)
{
    while (index < MAX_INDEX)
    {
        // dosomething
    } // 结束while (index < MAX_INDEX)
} // 结束if (...)

三、标识符命名

1.标识符的命名要清晰明了,有明确的含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
较短的单词可以通过去掉元音形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写。
例如:如下单词的缩写能够被大家基本认可
(1)temp缩写为tmp
(2)flag缩写为flg
(3)statistic缩写为stat
(4)increment缩写为inc
(5)message缩写为msg

2.通过对函数、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的。
清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释。

3.变量、函数、类、结构体都使用相连的单词命名,变量和函数其除第1个单词外其余单词的第1个字母大写,类和结构体的所有单词的第1个字母大写,单词之间不使用_进行连接。
例如:
(1)Uint32 numberOfArticles;
(2)Class ReserveCarUseController
(3)Customer identifyCustomer(const UniqueIdentifier& customerId);

4.对于变量命名,禁止取单个字符(如i,j,k…),建议除了要有具体含义外,还能表明其变量类型、数据类型等。但i,j,k作为局部循环变量是允许的。

5.除非必要,不要用数字或较奇怪的字符来定义标识符

// 不良的命名
#define _EXAMPLE_0_TEST_
#define _EXAMPLE_1_TEST_
void setSls00(Uint8, sls);

// 良好的命名
#define _EXAMPLE_UNIT_TEST_
#define _EXAMPLE_ASSERT_TEST_
void setUdtMsgSls(Int8 sls);

6.在同一软件产品内,应规划好接口部分标识符(变量、结构、函数及常量)的命名,防止编译、链接时产生冲突。

7.除了编译开关/头文件等特殊应用,应避免使用_EXAMPLE_TEST_之类以下划线开始和结尾的定义

四、可读性

1.注意运算符的优先级,并用括号明确表达式的操作顺序,避免使用默认优先级。

word = (high << 8) | low;
if ((a | b) && (a & c))
if ((a | b) < (c & d))

2.避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含有物理意义的常量,不应直接使用数字,必须用有意义的枚举或宏来代替

#define TRUNK_IDLE 0
#define TRUNK_BUSY 1

if (Trunk[index].trunkState == TRUNK_IDLE)
{
    Trunk[index].trunkState = TRUNK_BUSY;
}

五、变量、结构

1.去掉没必要的公共变量。
公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间的耦合度。

2.仔细定义并明确公共变量的含义、作用、取值范围及公共变量间的关系。
在对变量声明的同时,应对其含义、作用及取值范围进行注释说明,同时若有必要还应说明与其他变量的关系。

3.防止局部变量与公共变量同名。

4.严禁使用未经初始化的变量作为右值。
特别是在C中引用未经赋值的指针,经常会引起系统崩溃。
所以在编译选项中务必打开所有的警告,在gcc中是-Wall。在提交代码前务必排除掉所有的警告。

5.构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的公共变量,防止多个不同模块或函数都可以修改、创建同一个公共变量的现象。

6.使用严格形式定义的、可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。即统一使用可移植的类型定义,如:

// 无符号类型
typedef unsigned long long Uint64;
typedef unsigned long Uint32L;
typedef unsigned int Uint32;
typedef unsigned short Uint16;
typedef unsigned char Uint8;

// 有符号类型
typedef long long Int64;
typedef long Int32L;
typedef int Int32;
typedef short Int16;
typedef char Int8;

// 浮点数类型
typedef float Float32;
typedef double Float64;

// 布尔类型
typedef unsigned int Bool32;
typedef unsigned char Bool;

// 指针类型
typedef void* Ptr;

7.结构的功能要单一,是针对一种事务的抽象。
设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。

8.结构中元素的个数应适中。若结构中元素个数过多,可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。

typedef struct
{
    Uint8 name[8];
    Uint8 age;
    Uint8 sex;
} PersonBaseInfo;

typedef struct
{
    Uint8 addr[40];
    Uint8 city[15];
    Uint8 tel;
} PersonAddress;

typedef struct
{
    PersonBaseInfo personBase;
    PersonAddress personAddr;
} Person;

9.结构的设计要尽量考虑向前兼容和以后的版本升级,并为某些未来可能的应用保留余地(如预留一些空间等)
软件向前兼容的特性,是软件产品是否成功的重要标志之一。如果要想使产品具有较好的前向兼容,那么在产品设计之初就应为以后版本升级保留一定余地,并且在产品升级时必须考虑前一版本的各种特性。
为考虑向前兼容,定义模块的对外接口时,务必在数据结构的最后保留一定数量的字段,具体数量依据程序的完善程度而定。如果程序已经比较完善,可保留4个Uint32数量的字节;如果不完善,至少保留8个Uint32数量的字节。在32位处理器上,结构体对齐到32位,可避免因编译环境不同而导致结构大小不一致的情况,所以添加保留字段时要注意这一点。
考虑到效率、扩展性、维护性和美观性,不建议使用位域字段

typedef struct
{
    // 用户的回调接口和数据
    PSP_EdmaCbParams cbParams;

    // 选择通道号 总共64个通道 值范围是0-63
    Uint16 nChId;

    // 选择EDMA队列
    Uint16 nEventR;

    Uint16 reserved[32];
} PSP_EdmaChInitParams;

10.合理的设计数据,尽量减少没有必要的数据类型默认转换和强制转换。

11.在栈上(静态定义变量)不能定义超过系统规定(8MB大小)的数组和对象的实例,一般指超大数组。若需要定义大型的变量,应改从堆中申请(动态申请)。

六、函数

1.对函数的输入数据必须要仔细、全面地处理,对所调用函数的返回值要仔细、全面地处理。
检查输入参数时,其中一项最主要内容是检查参数指针是否为NULL。
对外提供的函数接口,内部数检查一定要详尽,对于指针参数一定要作断言。

2.明确函数功能,精确而不是近似地实现函数设计。

3.编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。

Uint32 example(Int32 para)
{
    Uint32 temp;

    // 申请信号量操作
    Exam = para;
    temp = SquareExam();
    // 释放信号量操作
    return temp;
}

4.在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责。

5.防止将函数的参数作为工作变量。
将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。

void sumData(Uint32 num, Int32 *pData, Int32 *pSum)
{
    Uint32 count;
    Int32 sumTemp = 0;

    for (count = 0; count < num; count++)
    {
        sumTemp += pData[count];
    }

    *pSum = sumTemp;
}

6.一个函数仅完成一项功能。

7.函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出。
而带有内部存储器的函数的功能可能是不可预测的,这样的函数既不易于理解也不利于测试和维护。在C语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测。

8.避免设计多参数函数,参数控制在4个以内,不使用的参数从接口中去掉,尽量不要使用类型和数目不确定的参数
其目的是减少函数间接口的复杂度。对于大多数编译器而言,超过4个参数则会使用栈传递参数,效率会降低,也影响美观。
如果超过4个参数,则将相近的参数定义在结构体中,用结构体指针传递。若不想修改参数值,加const进行修饰。

9.非调度函数应减少或防止控制参数,尽量只使用数据参数。
其目的是防止函数间的控制耦合。调度函数是指根据输入的消息类型或控制命令,来启动相应的功能实体(即函数),而本身并不完成具体功能。控制参数是指改变函数功能行为的参数,即函数要根据此参数来决定具体怎样工作。非调度函数的控制参数增加了函数间的控制耦合,很可能使函数间的耦合度增大,并使函数的功能不唯一。

10.检查函数所有非参数输入的有效性,如数据文件、公共变量等。
函数的输入主要有2种:一种是参数输入,另一种是全局变量、数据文件输入,即非参数输入。函数在使用输入之前,应进行必要的检查。

11.设计高扇入、合理扇出(小于7)的函数。
扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利于程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5.扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。
扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。
良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。

12.不允许函数本身或函数间的递归调用
递归调用特别是函数间的递归调用(如A->B->C->A),影响程序的可理解性。递归调用一般都占用较多的系统资源(如栈空间)。递归调用对程序的测试有一定影响。嵌入式设备堆栈资源有限,递归调用很容易导致栈溢出。

13.避免使用Bool作为参数或返回值
其原因是Bool参数值无意义,True/False的含义非常模糊,在调用时很难知道该参数到底传达的是什么意思。另外Bool参数不利于扩充。另NULL也是一个无意义的单词。
但可以使用自定义的Bool类型,使用时变量名称务必要有二值意义,如Bool isSwitchSensor。推荐使用Bool32类型,其是一个32位的Bool。

14.对于提供了返回值的函数,在引用时最好使用其返回值

15.尽量不要使用malloc等动态堆分配函数
大部分情况不允许使用malloc,特别是频繁地申请和释放内存。但是初始化队列或创建句柄的时候可以使用。也就是说如果分配的内存生存周期是从程序加载开始到程序退出为止,那么可以使用动态内存申请释放的方式。

16.若函数被多线程、多进程调用,需通过锁、信号量进行保护。

17.不使用strcpy、gets等不安全的函数,而是使用fgets、snprintf等安全函数。

18.打印输出信息时,应定义使用debugf、errorf、infof、warnf、tracef等函数进行打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值