C语言编程规范

C语言编程规范 ---- 1 排版

  • 1.1 严格采用阶梯层次组织程序代码

各层次缩进的风格采用TAB缩进(TAB宽度原则上使用系统默认值)

  • 1.2空行

(1)变量说明之后必须加空行。

(2)相对独立的程序块之间应加空行。

  • 1.3 对变量的定义,尽量位于函数的开始位置

(1)应避免分散定义变量。

(2)同一类的变量在同一行内定义,或者在相邻行定义。

(3)数组、指针等复杂类型的定义放在定义区的最后。

(4)变量定义区不做较复杂的变量赋值。

  • 1.4 程序各部分的放置顺序

在较小的项目中,按如下顺序组织安排程序各部分:

(1)#include <C的标准头文件>

(2)#include 〞用户自定义的文件〞

(3)#define 宏定义。

(4)全局变量定义。

(5)函数原型声明。

(6)main函数定义。

(7)用户自定义函数。

以上各部分之间、用户自定义的函数之间应加空行。注意,函数原型声明统一集中放在main函数之前,不放在某个函数内部。

 

C语言编程规范 ---- 2 注释

  • 2.1 注释的原则

注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。 示例:如下注释意义不大。

/* if receive_flag is TRUE */
if (receive_flag)

而如下的注释则给出了额外有用的信息。

/* if mtp receive a message from links */
if (receive_flag)
  • 2.2 说明性文件头部应进行注释

说明性文件(如头文件.h 文件、.inc 文件、.def 文件、编译说明文件.cfg 等)头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。

示例:下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

/**
 * Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
 * File name:      // 文件名
 * Author:       Version:        Date: // 作者、版本及完成日期
 * Description:    // 用于详细说明此程序文件完成的主要功能,与其他模块
                  // 或函数的接口,输出值、取值范围、含义及参数间的控
                  // 制、顺序、独立或依赖等关系
 * Others:         // 其它内容的说明
 * Function List:  // 主要函数列表,每条记录应包括函数名及功能简要说明
    1. ....
 * History:        // 修改历史记录列表,每条修改记录应包括修改日期、修改
                  // 者及修改内容简述
    1. Date:
       Author:
       Modification:
   2. ...
 */
  • 2.3 源文件头部应进行注释

源文件头部应进行注释,列出:版权说明、版本号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。

示例:下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

说明:Description一项描述本文件的内容、功能、内部各部分之间的关系及本文件与其它文件关系等。History是修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述。

/**
 * Copyright (C), 1988-1999, Huawei Tech. Co., Ltd.
 * FileName: test.cpp
 * Author:        Version :          Date:
 * Description:     // 模块描述
 * Version:         // 版本信息
 * Function List:   // 主要函数及其功能
    1. -------
 * History:         // 历史修改记录
      <author>  <time>   <version >   <desc>
      David    96/10/12     1.0     build this moudle
 */
  • 2.4 函数头部应进行注释

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

示例1:下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。

对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。

在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。

/**
 * Function:       // 函数名称
 * Description:    // 函数功能、性能等的描述
 * Calls:          // 被本函数调用的函数清单
 * Called By:      // 调用本函数的函数清单
 * Table Accessed: // 被访问的表(此项仅对于牵扯到数据库操作的程序)
 * Table Updated:  // 被修改的表(此项仅对于牵扯到数据库操作的程序)
 * Input:          // 输入参数说明,包括每个参数的作
                  // 用、取值说明及参数间关系。
 * Output:         // 对输出参数的说明。
 * Return:         // 函数返回值的说明
 * Others:         // 其它说明
 */
  • 2.5 进行注释时的注意事项

(1)建议边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除。

(2)注释的内容要清楚、明了,含义准确,防止注释二义性。说明:错误的注释不但无益反而有害。

(3)避免在注释中使用缩写,特别是非常用缩写。在使用缩写时或之前,应对缩写进行必要的说明。

(4)注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面。除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。

示例:如下例子不符合规范。

//例1:
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

//例2:
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;
/* get replicate sub system index and net indicator */

//应如下书写
/* get replicate sub system index and net indicator */
repssn_ind = ssn_data[index].repssn_index;
repssn_ni = ssn_data[index].ni;

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

示例:

/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */

(6)数据结构声明( 包括数组、结构、类、枚举等) ,如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。

示例:可按如下形式说明枚举/数据/联合结构。

/* sccp interface with sccp user primitive message name */
enum  SCCP_USER_PRIMITIVE
{
    N_UNITDATA_IND, /* sccp notify sccp user unit data come */
    N_NOTICE_IND,   /* sccp notify user the No.7 network can not */
                    /* transmission this message */
    N_UNITDATA_REQ, /* sccp user's unit data transmission request*/
};

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

示例:

/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */      // 变量作用、含义
/* 0 - SUCCESS   1 - GT Table error */
/* 2 - GT error  Others - no use  */       // 变量取值范围
/* only  function  SCCPTranslate() in */
/* this modual can modify it,  and  other */
/* module can visit it through call */
/* the  function GetGTTransErrorCode() */    // 使用方法
BYTE g_GTTranErrorCode;

(8)注释与所描述内容进行同样的缩排,让程序排版整齐,并方便注释的阅读与理解。

示例:如下例子,排版不整齐,阅读稍感不方便。

void example_fun( void )
{
/* code one comments */
    CodeBlock One
        /* code two comments */
    CodeBlock Two
}

应改为如下布局。

void example_fun( void )
{
    /* code one comments */
    CodeBlock One
    /* code two comments */
    CodeBlock Two
}

(9)将注释与其上面的代码用空行隔开。

示例:如下例子,显得代码过于紧凑。

/* code one comments */
program code one
/* code two comments */
program code two

应如下书写

/* code one comments */
program code one

/* code two comments */
program code two

(10)对变量的定义和分支语句(条件分支、循环语句等)必须编写注释。这些语句往往是程序实现某一特定功能的关键,对于维护人员来说,良好的注释帮助更好的理解程序,有时甚至优于看设计文档。

(11)对于switch 语句下的case 语句,如果因为特殊情况需要处理完一个case 后进入下一个case 处理(即上一个case后无break),必须在该case 语句处理完、下一个case 语句前加上明确的注释,以清楚表达程序编写者的意图,有效防止无故遗漏break语句(可避免后期维护人员对此感到迷惑:原程序员是遗漏了break语句还是本来就不应该有)。示例:

case CMD_DOWN:
    ProcessDown();
    break;
case CMD_FWD:
    ProcessFwd();
    if (...)
    {
        ...
        break;
    } 
    else
    {
        ProcessCFW_B();   // now jump into case CMD_A
    }
case CMD_A:
    ProcessA();
    break;
...

(12)在程序块的结束行右方加注释标记,以表明某程序块的结束。当代码段较长,特别是多重嵌套时,这样做可以使代码更清晰,更便于阅读。示例:参见如下例子。

if (...)
{
    program code
    while (index < MAX_INDEX)
    {
        program code
    } /* end of while (index < MAX_INDEX) */ // 指明该条while语句结束
} /* end of  if (...)*/ // 指明是哪条if语句结束

(13)在顺序执行的程序中,每隔3—5行语句,应当加一个注释,注明这一段语句所组成的小模块的作用。对于自己的一些比较独特的思想要求在注释中标明。

(14)注释格式尽量统一,建议使用“/* …… */”。

(15)注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达——注释语言不统一,影响程序易读性和外观排版,出于对维护人员的考虑,建议使用中文。

 

C语言编程规范 ---- 3 命名规则

  • 3.1 命名的基本原则

标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解——尽量采用采用英文单词或全部中文全拼表示

  • 3.2 变量名的命名规则

(1)变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思、英文的缩写、中文全拼或中文全拼的缩写,要求单词的第一个字母应大写。

即: 变量名=变量类型+变量的英文意思(或英文缩写、中文全拼、中文全拼缩写)对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。见下表:

bool 用b开头 bFlg

int 用i开头 iCount

short int 用n开头 nStepCount

long int 用l开头 lSum

char  用c开头 cCount

unsigned char 用by开头

float 用f开头 fAvg

double 用d开头 dDeta

unsigned int(WORD) 用w开头 wCount

数组 (Array)     用a开头                     

unsigned long int(DWORD) 用dw开头 dwBroad

字符串 用s开头 sFileName

用0结尾的字符串 用sz开头 szFileName

注意:对于变量命名,禁止取单个字符(如i 、j 、k...),建议除了要有具体含义外,还能表明其变量类型、数据类型等,但i、j、k作局部循环变量是允许的,也不需要加前缀标识变量类型。

(2)指针变量命名的基本原则为:

对一重指针变量的基本原则为:“p”+变量类型前缀+命名,如一个float*型应该表示为pfStat。对二重指针变量的基本规则为:“pp”+变量类型前缀+命名。对三重指针变量的基本规则为:“ppp”+变量类型前缀+命名。

(3)全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)。此规则还可避免局部变量和全局变量同名而引起的问题。

(4)静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)

(5)对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。如:

enum cmEMDAYS
{
    EMDAYS_MONDAY;
    EMDAYS_TUESDAY;
    ……
};

(6)对struct、union变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。结构一般用S开头,如:

struct ScmNPoint
{
    int nX;//点的X位置
    int nY; //点的Y位置
};

联合体一般用U开头,如:

union UcmLPoint
{
    LONG lX;
    LONG lY;
}

(7)对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。当需要由多个单词表示时,单词与单词之间必须采用连字符“_”连接。

如:

#define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) //其中CM表示类别。

(8)对const 的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;示例:

const char* c_szFileName;
  • 3.4 函数的命名规范

(1)函数的命名应该尽量用英文(或英文缩写、中文全拼、中文全拼缩写)表达出函数完成的功能——函数名应准确描述函数的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。函数名首字大写,若包含有两个单词的每个单词首字母大写。如果是OOP 方法,可以只有动词(名词是对象本身)。示例:

LONG GetDeviceCount(……);
void print_record( unsigned int rec_ind ) ;
int  input_record( void ) ;
unsigned char get_current_color( void ) ;

(2)避免使用无意义或含义不清的动词为函数命名。如使用process、handle等为函数命名,因为这些动词并没有说明要具体做什么。

(3)必须使用函数原型声明。函数原型声明包括:引用外来函数及内部函数,外部引用必须在右侧注明函数来源: 模块名及文件名;内部函数,只要注释其定义文件名——和调用者在同一文件中(简单程序)时不需要注释。

应确保每个函数声明中的参数的名称、类型和定义中的名称、类型一致。

  • 3.5 函数参数命名规范

(1)参数名称的命名参照变量命名规范。

(2)为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。

(3)为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:

……cmCopyString(const CHAR * c_szSource, CHAR * szDest)

C语言编程规范 ---- 4 可读性

  • 4.1使用有意义的标识,避免直接使用数字

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

示例:如下的程序可读性差。

if (Trunk[index].trunk_state == 0)
{
    Trunk[index].trunk_state = 1;
    ...  // program code
}

应改为如下形式。

#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
    Trunk[index].trunk_state = TRUNK_BUSY;
    ...  // program code
}

C语言编程规范 ---- 5 变量与结构

  • 5.1 谨慎使用全局(公共)变量

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

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

(3)明确公共变量与操作此公共变量的函数或过程的关系,如访问、修改及创建等。明确过程操作变量的关系后,将有利于程序的进一步优化、单元测试、系统联调以及代码维护等。这种关系的说明可在注释或文档中描述。示例:在源文件中,可按如下注释形式说明。

全局变量    函数    System_Init Input_Rec   Print_Rec   Stat_Score

Student Create  Modify  Access  Access

Score   Create  Modify  Access  Access, Modify

注: Create表示创建,Modify表示修改,Access表示访问。

其中,函数Input_Rec、Stat_Score都可修改变量Score,故此变量将引起函数间较大的耦合,并可能增加代码测试、维护的难度。

(4)当向公共变量传递数据时,要十分小心,防止赋与不合理的值或越界等现象发生。对公共变量赋值时,若有必要应进行合法性检查,以提高代码的可靠性、稳定性。

(5)防止局部变量与公共变量同名——通过使用较好的命名规则来消除此问题。

  • 5.2 合适地定义和使用结构

结构的功能要单一,是针对一种事务的抽象

 

C语言编程规范 ---- 6 函数与过程

  • 6.1 函数的功能与规模设计

函数应当短而精美,而且只做一件事。不要设计多用途面面俱到的函数,多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。

  • 6.2 函数的返回值

(1)对于函数的返回位置,尽量保持单一性,即一个函数尽量做到只有一个返回位置。(单入口单出口)。

要求大家统一函数的返回值,所有的函数的返回值都将以编码的方式返回。

例如编码定义如下:

#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:

参考函数实现如下:

LONG 函数名(参数,……)
{
    LONG lResult; //保持错误号
    lResult=CM_OK;
    //如果参数有错误则返回错误号
    if(参数==NULL)
    {
        lResult=CM_POINT_IS_NULL;
        goto END;
    }
    ……
    END:
    return lResult;
}

调用者对所调用函数的错误返回码要仔细、全面地处理

  • 6.3 变量的使用

当你确实需要时才用全局变量,函数间应尽可能使用参数、返回值传递消息。

  • 6.4 函数参数

在同一项目组应明确规定对接口函数参数的合法性检查

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

(2)避免设计多参数函数,不使用的参数从接口中去掉,目的减少函数间接口的复杂度。

C语言编程规范 ---- 7 可测性

  • 7.1 准备测试代码、测试用例

(1)编程的同时要为单元测试选择恰当的测试点,并仔细构造测试代码、测试用例,同时给出明确的注释说明。测试代码部分应作为(模块中的)一个子模块,以方便测试代码在模块中的安装与拆卸(通过调测开关)

(2)在进行集成测试/ 系统联调之前,要构造好测试环境、测试项目及测试用例,同时仔细分析并优化测试用例,以提高测试效率。好的测试用例应尽可能模拟出程序所遇到的边界值、各种复杂环境及一些极端情况等。

(3)在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码如打印函数等。程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、高率的测试并尽可能地找出软件中的错误就成为很关键的问题。因此在编写源代码之前,除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集成测试及系统联调提供方便。

  • 7.2 使用断言来发现软件问题,提高代码可测性

(1)断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。

示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)

#ifdef _EXAM_ASSERT_TEST_  // 若使用断言测试
void exam_assert( char * file_name, unsigned int line_no )
{
    printf( "\n[EXAM]Assert failed: %s, line %u\n",
            file_name, line_no );
    abort( );
}

#define  EXAM_ASSERT( condition )
    if (condition) // 若条件成立,则无动作
        NULL;
    else  // 否则报告
        exam_assert( __FILE__, __LINE__ )
#else  // 若不使用断言测试
#define EXAM_ASSERT(condition)  NULL
#endif  /* end of ASSERT */

(2)用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况。

(3)不能用断言来检查最终产品肯定会出现且必须处理的错误情况。断言是用来处理不应该发生的错误情况的,对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。

(4)对较复杂的断言加上明确的注释,澄清断言含义并减少不必要的误用。

(5)用断言确认函数的参数。示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。

int exam_fun( unsigned char *str )
{
    EXAM_ASSERT( str != NULL );  // 用断言检查“假设指针不为空”这个条件
    ... //other program code
}
  • 7.3 版本控制

(1)正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉),加快软件运行速度。

(2)在软件系统中设置与取消有关测试手段,不能对软件实现的功能等产生影响——即有测试代码的软件和关掉测试代码的软件,在功能行为上应一致。

(3)用调测开关来切换软件的DEBUG 版和正式版,而不要同时存在正式版本和DEBUG 版本的不同源文件,以减少维护的难度

(4)软件的DEBUG 版本和发行版本应该统一维护,不允许分家,并且要时刻注意保证两个版本在实现功能上的一致性。

C语言编程规范 ---- 8 效率

在保证软件系统的正确性、稳定性、可读性及可测性的前提下,提高代码效率。不能一味地追求代码效率,而对软件的正确性、稳定性、可读性及可测性造成影响。

发布了49 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/auspark/article/details/103234424