C语言:代码规范浅谈&摘录(包含【华为代码规范】部分规则摘录)




1.概述

此文件为C语言软件代码规范,其中规范主要分为应用层代码规范和驱动代码规范。应用层、驱动层代码规范大同小异,如遇不同的地方会单独进行拆分说明,否则将共同遵守。

1.1.编写目的

本规范撰写目的主要为了软件开发人员进行代码编写时,提供参考依据和统一标准。

1.2.文档约定

(1)通篇规范将会逐条罗列出每个要求的具体规则与实现示例。

(2)文中所有示例会展示正确示例和错误示例,正确示例用Good进行标识,错误示例用Bad进行标识。

(3)示例代码统一采用绿色8号黑体字体(等宽)。




2.排版要求

2.1.代码风格

规则1:外部开源代码、内核代码、第三方代码、等各自保持源代码风格,不必强行将所有代码完全统一,自写代码必须遵守本规范。

2.2.头文件(h文件)排版顺序

规则2:头文件里包括注释、预处理指令、宏定义、引用文件、结构体类型变量定义、函数声明、全局变量声明,并按照如下顺序进行编制。

  1. 头文件注释
  2. 引用文件
  3. 预处理指令
  4. 结构体类型定义
  5. 函数声明
  6. 全局变量外部引用声明(含:结构体类型变量和非结构体类型变量等)

2.3.源文件(c文件)排版顺序

规则3:文件里包括注释、预处理指令、宏定义、引用文件、结构体类型变量定义、函数声明、全局变量声明,并按照如下顺序进行编制。

  1. 头文件注释
  2. 引用文件
  3. 预处理指令
  4. 结构体类型/变量定义
  5. 函数定义

2.4.引用文件顺序

规则4:.c .h文件中的引用文件(#include xxx),由上至下按照先不稳定后稳定性排序。

备注:稳定文件一般指库中自有文件,如<stdio.h><string.h>等,不稳定文件一般指开发人员书写文件,如”led.h”,”myCaculate.h”等。当程序较大编译时间较长时,不稳定文件排在上面,如果出错会直接停止编译,节省开发时间。

示例Bad:

#include <stdio.h>
#include "caculate.h"
#include <stdlib.h>
#include "spi.h"
#include <string.h>

示例Good:

#include "spi.h"
#include "caculate.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

2.5.缩进 & 对齐(空格/Tab)

规则5:缩进统一采用4空格缩进,禁止使用Tab。对齐同理。对于使用开发工具自动生成的代码可以有不一致。

说明:不同编译器对TAB所设置的空格数目不同,混用更换编译器之后将会导致代码显得杂乱无比。而所有编译器对空格都是固定的,一个空格宽度就是一个字符。特别是注释中的制表,如果使用TAB换个编译器极容易出现错位情形,而使用空格不管使用任何编译器都不会错位。比如如下制表代码,如果使用TAB对齐,在不同的编译器中非常容易错乱。

示例Bad:

/*+------+------+------+------+------+------+------+------+------+
  | 字节	|	bit8	| bit7 | bit6	|	bit5 | bit4 | 	bit3 | 	bit2 | 	bit1 | 
+------+------+------+------+------+------+------+------+------+
  | 含义 |数据头|	 命 令		|    数 据	|	和校验	|	数据尾	|
+------+------+------+------+------+------+------+------+------+*/

示例Good:

/*  +------+------+------+------+------+------+------+------+------+
 *  | 字节 | bit8 | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 |
 *  +------+------+------+------+------+------+------+------+------+
 *  | 含义 |数据头|    命 令     |    数 据    |    和校验   |数据尾 |
 *  +------+------+------+------+------+------+------+------+------+
 */

规则6:缩进风格选择K&R缩进风格或BSD缩进风格均可,但是要统一。建议使用K&R缩进风格,可以让代码看起来更加紧凑。

几种常见的缩进风格如下:

(1)K&R缩进风格:

  • 函数左大括号另起一行置于行首,且独占一行。其余情况(如if/while/for/else/do等关键字)对应的左花括号放置于前一行行尾,不独占一行。右大括号独占一行。

    void func(xxx)
    {
        if (xxx) {
            xxx;
        } else {
            xxx;
        }
    
        while (xxx) {
            xxx;
        }
    
        for (xxx) {
            xxx;
        }
    }
    

(2)BSD缩进风格:

  • 左右花括号均独占一行。

    void func(xxx)
    {
        if (xxx)
        {
            xxx;
        } 
        else
        {
            xxx;
        }
    
        while (xxx)
        {
            xxx;
        }
    
        for (xxx)
        {
            xxx;
        }
    }
    

(3)Whitesmith风格:

  • 大括号和代码一起缩进

    if (xxx)
        {
        xxx;
        }
    

(4)GNU风格:

  • 缩进2个空格

    if (xxx)
      {
        xxx;
      }
    

2.6.空行

规则7:禁止每行代码都加空行,仅需在相对独立的代码块之间用一行空行隔开即可。

示例Bad:

#define XXX XXX 

void func(xxx) {

    int temp;

    char sendBuf[xxx];
 
    char recvBuf[xxx];

    temp = SendData(xxx); 

    if (temp < 0) {

        printf("xxx");

        return xxx;

    }

    return xxx;

}

示例Good:

#define XXX XXX 

void func(xxx)
{
    int temp;
    char sendBuf[xxx];
    char recvBuf[xxx];

    temp = SendData(xxx); 
    if (temp < 0) {
        printf("xxx");
        return xxx;
    }

    return xxx;
}

规则8:严禁一次连续使用多个相邻的空行。

示例Bad:

#define XXX XXX
#define XXX XXX
#define XXX XXX



void func(xxx)
{
    xxx
}

示例Good:

#define XXX XXX
#define XXX XXX
#define XXX XXX

void func(xxx)
{
    xxx
}

2.7.空格

规则9:除了代码对齐时需要添加必要的空格之外,其他必要的地方不要出现连续空格。对齐时输入多少空格应该保证最长的宏字符串与数字之间空格不超过4个。

示例Bad:

#define GPIO_STR_HEAD                              "GPIO"
#define GPIO_STR_SPLIT_CHAR                        "_"
#define GPIO_STR_LEN_MAX                           32
#define GPIO_NUM_KIND                              2

#define GPIO_GROUP_MAX                             6
#define GPIO_PIN_MAX                               32
#define GPIO_PERGROUP_TOTAL_PIN                    32

示例Good:

#define GPIO_STR_HEAD           "GPIO"
#define GPIO_STR_SPLIT_CHAR     "_"
#define GPIO_STR_LEN_MAX        32
#define GPIO_NUM_KIND           2

#define GPIO_GROUP_MAX          6
#define GPIO_PIN_MAX            32
#define GPIO_PERGROUP_TOTAL_PIN 32

示例Good:

#define GPIO_STR_HEAD        "GPIO"
#define GPIO_STR_SPLIT_CHAR  "_"
#define GPIO_STR_LEN_MAX     32
#define GPIO_NUM_KIND        2

#define GPIO_GROUP_MAX          6
#define GPIO_PIN_MAX            32
#define GPIO_PERGROUP_TOTAL_PIN 32

2.8.变量定义位置

规则10:严禁在函数内部随处定义变量,所有的变量必须在函数开始时统一定义。

示例Bad:

void func(xxx) {
    int temp;

    if (flag != 0) {
        char recvBuf[xxx];
        char sendBuf[xxx];

        temp = SendData(xxx); 
        printf("xxx");

        return xxx;
    }

    return xxx;
}

示例Good:

void func(xxx) {
    int temp;
    char recvBuf[xxx];
    char sendBuf[xxx];

    if (flag != 0) {
        temp = SendData(xxx); 
        printf("xxx");

        return xxx;
    }

    return xxx;
}

2.9.行宽:长语句 & 长表达式

规则11:代码行宽不得超过120个字符(特殊情况可以例外,如宏定义一个长字符串的情况可以突破此限制,因为字符串无法换行分割)。

规则12:对于条件语句或函数参数较多的情况下,行宽亦不得超过120字符,若超过则需要进行合理换行,换行并对齐后,再采用4字符缩进,条件符号置于上行行尾。

示例Bad:

if (xxxxxxxxxxxxxxx && xxxxxxxxxxxxxxx && xxxxxxxxxxxxxxx && xxxxxxxxxxxxxxx && xxx && xxx) {
    /* code */;
}

示例Good:

if (xxxxxxxxxxxxxxx && xxxxxxxxxxxxxxx && xxxxxxxxxxxxxxx &&
    xxxxxxxxxxxxxxx && xxx && xxx) {
    /* code */;
}

2.10.函数行数

规则13:函数有效行数不得超过80行,超过情况需考虑独立子功能提炼封装新的功能函数。

说明:有效行数指除空行之外的有效代码行数,对于行宽超过80字符导致换行情况算2行。

2.11.长参数

规则14:函数参数个数不得超过5个,超过的情况考虑采用结构体进行封装。

2.12.短语句

规则15:禁止将多个短语句置于一行,即每行只写一条语句。

说明:如C语言中可以简单理解为,每行最多出现一次分号。

示例Bad:

int temp; int value;

示例Good:

int temp;
int value;

2.13.条件循环语句

规则16:if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分哪怕只有一行也必须要加花括号{}。

示例bad:

if (flag != 0) temp = SendData(xxx);

示例bad:

if (flag != 0)
    temp = SendData(xxx);

示例good:

if (flag != 0) {
    temp = SendData(xxx); 
}

2.14.花括号(程序块分界符)(K&R风格)

规则17:函数左右花括号均独占一行。

规则18:关键字如if、for、while等左花括号跟随上一行行尾不用独占一行;右花括号独占一行,但是如果右花括号之后还有跟随语句剩余部分(如:else if、do-while语句的右花括号无需独占一行)。

示例Bad:

void Foo(void)
{
    xxx;
    
    if (xxx)
    {
        xxx;
    } 
    else if (xxx)
    {
        xxx;
    } 
    else
    {
        xxx;
    }
    
    do
    {
        xxx;
    }
    while (xxx);
}

示例Good:

void Foo(void)
{
    xxx;
    
    if (xxx) {
        xxx;
    } else if (xxx) {
        xxx;
    } else {
        xxx;
    }
    
    do {
        xxx;
    } while (xxx);
}

规则19:花括号不能再没有依赖语句的情况下独自出现。

示例bad:

{
    temp = SendData(xxx);
    if (xxx) {
        xxx
    }

    temp = RecvData(xxx);
    if (xxx) {
        xxx
    }
}

示例good:应删除掉上述代码中冗余的花括号

temp = SendData(xxx);
if (xxx) {
    xxx
}

temp = RecvData(xxx);
if (xxx) {
    xxx
}

2.15.操作符前后空格

规则20:逗号分隔符只在后面加1个空格。

示例Bad:

int temp , value , recvData;

示例Good:

int temp, value, recvData;

规则21:对单个关键字、变量、常量进行”地址运算&”、”指针运算”、”取非!”、”取反~”、”自增++”、”自减–”等单目运算符操作时,变量与操作符之间相邻保持紧促不加空格。*

示例Bad:

* p = NULL;
p = & data;
* p ++;

示例Good:

*p = NULL;
p = &data;
(*p)++;

规则22:对两个以上关键字、变量等进行对等操作时在操作符前后均加一个空格。(如:”比较操作符 ==”、”赋值操作符 =”、”算数操作符 +、-、+=、-=”、 ”逻辑操作符 &&、按位与&、||、|”、 ”位域操作符 <<、>>”)

示例Bad:

temp=RecvData(xxx);
if (temp==strlen(xxx)) {
   xxx
}

示例Good:

temp = RecvData(xxx);
if (temp == strlen(xxx)) {
   xxx
}

规则23:对两个以上关键字、变量等进行非对等操作时不加空格。(如:结构体成员索引符号 -> 和 . )

示例Bad:

if (ledDrv . major == xxx) {
    xxx
}
if (recvData -> len == xxx) {
    xxx
}

示例Good:

if (ledDrv.major == xxx) {
    xxx
}
if (recvData->len == xxx) {
    xxx
}

规则24:if、for、while、switch等关键字与之后括弧之间加1空格,从而使关键字更加突出明显。

示例Bad:

if(xxx) {
    xxx
}

if    (xxx)     {
    xxx
}

示例Good:

if (xxx) {
    xxx
}

规则25:函数名与函数参数的左括弧之间操持紧凑,不加空格。

示例Bad:

void func (xxx) {
    xxx
}

示例Good:

void func(xxx) {
    xxx
}

2.16.避免过度依赖优先级

规则26:避免过度依赖优先级,应多依赖括号,在必要的地方添加括号保证程序的易读性。

如:并列条件语句应对每个条件均加上括号,运算符应增加必要的括号。

示例Bad:

ret = age++ + *p++;

if (recvData->len >= xxx || recvData->len <= xxx) {
    xxx
}

示例Good:

ret = (age++) + ((*p)++);

if ((recvData->len >= xxx) || (recvData->len <= xxx)) {
    xxx
}

2.17.宏函数

规则27:宏函数应该按照正常语句换行格式进行书写,行末加上换行符即可。

示例Bad:

#define MACRO(x, y) do { stmt1; stmt2;} while(0)

示例Good:

#define MACRO(x, y) \
do {                \
    xxx;            \
    xxx;            \
} while (0)



3.注释规范

3.1.文件头注释

规则28:头文件注释应当包含:功能描述、作者、创建时间、最后修改时间、修改人、修改内容等。具体格式示例如下:

/* =========================================================================
 * DESCRIPTION:
 *     文件内容描述。
 * =========================================================================
 * AUTHOR: 
 * CREATE DATE: 
 * Modifier      LSAT Modify Time      Modify Concent
 * LSY           20200521              增加xxx功能。
 * LSY           20200530              修改xxx。
 * =========================================================================*/

3.2.常规注释

规则29:在能说明问题的基础上尽量简洁。

规则30:(建议)注释统一使用一种类型语言进行注释。

说明:建议使用中文,且少使用或不使用中英混搭,避免英文表达不准确的情况。

规则31:注释使用同一种风格进行注释,建议统一使用/* */风格。

示例good:

/* xxx */

/* xxx
 * xxx
 * xxx
 */

规则32:禁止使用注释嵌套。

示例bad:

/*  /* */ */
    /* // */
    //    /* */

规则33:不用的代码直接删除,不得采用注释的方法让其不生效。

规则34:避免在一行代码中间插入注释,使代码的可阅读性变差。




4.命名规范

4.1.命名综述

命名主要包含文件名、宏、常亮、枚举值、变量等。各类型命名规范如下表格:

备注:此表格为应用层命名规范,驱动层代码全部统一使用:纯小写字母和下划线的形式。

表格 4‑1 应用层命名规范综述
序号类型规范举例 & 备注
1文件名全小写,下划线分割
2
常量
枚举值
goto标签
全大写、下划线分割
4函数
枚举类型
结构体类型
联合体类型
大驼峰定义的函数中,每个模块对外公布的函数接口,可以在大驼峰的基础上增加:全大写模块名缩写前缀,用下划线分割。模块内部使用的函数可以不加。例如: xxx GPIO_GetLevel(xxx);
6全局变量
局部变量
函数参数
宏参数
结构体成员
联合体成员名
小驼峰(1) 全局变量可以加上前缀:g_ (建议项)
(2) 局部变量尽量简短,上下文能看出含义,就不用命名太详细。
(3) 范围越大的变量,应当命名越精细
                    

4.2.缩写

规则35:使用完整的单词或业界公认的缩写,避免使人产生误解。

示例:如下表格为目前业内大家公认的缩写

表格 4‑2 常见缩写
缩写全词缩写全词缩写全词
addraddressinfoinformationretreturn
appapplicationinsinsertscrscreen
argargumentlenlengthsecsecond
bkbackliblibrarysegsegment
btnbuttonlnklinkselselect
bufbuffloglogicalsrcsource
calccalculatemaxmaximumstdstandard
charcharactermemmemorystrstring
clkclickmidmiddlesubsubtract
cmpcompareminminimumsumsummation
colcolumnmsgmessagesvrserver
cpycopymulmultiplysyncsynchronization
dbdatabasenumnumbersyssystem
deldeleteobjobjecttbltable
destdestinationorgorigin
original
temp/tmptemporary
devdeviceparaparametertran/transtranslate
transation
transparent
diffdifferentpicpicturetxttext
divdividepkgpackageunkunknown
docdocumentpospositionupdupdate
drvdriverpre/prevpreviousupgupgrade
envdnvironmentprgprogramutilutility
errerrorprocprocess
procedure
valvalue
execexecutepswpasswordvarvariable
flgflagptrpointerverversion
func/fnfunctionpubpublicvertvertical
imgimageregregistervirvirus
incincreasereqrequestwndwindow

4.3.typedef

规则36:通过typedef对结构体、联合体、枚举取别名时,尽量使用匿名类型,若需要指针自嵌套、可以增加tag前缀,或者下划线后缀。

示例good1:

typedef struct {
    int a,
    char b,     
}MyType;

示例good2:

typedef struct tagNode {
    struct tagNode *prev,
    struct tagNode *next,
    int a,
    char b,     
}Node;

示例good3:

typedef struct Node_ {
    struct Node_ *prev,
    struct Node_ *next,
    int a,
    char b,     
}Node;

规则37:禁止私自通过typedef、#define对基本数值类型进行重定义,除非有明确必要性,应当由模块、产品、应用平台等统一定义一套基本类型别名。

4.4.宏

规则38:宏命名使用纯大写加下划线方式,前面再加上模块前缀。

示例Bad:这样的宏看不出来是哪个模块在使用,应该在前面加上模块前缀,否则不同的模块宏命名非常容易冲突。

#define SEND_BUFF_MAX 256
#define RECV_BUFF_MAX 256

示例Good:

#define UART1_SEND_BUFF_MAX 256
#define UART1_RECV_BUFF_MAX 256

规则39:宏定义中禁止使用数字直译(魔鬼数字),应该以实际功能命名字符串

示例Bad:这样的宏毫无意义,且后续维护性很差,应该使用数字的确切含义进行命名

#define SIXTEEN 16

示例Good:

#define CMD_STR_LEN_MAX 16

规则40:宏定义中禁止使用类似于魔鬼数字类型的直译字符串(魔鬼字符串),应该以实际功能命名字符串。

示例bad:“魔鬼字符串”,后续极难维护,应该使用字符串的实际功能命名,并且在前面加上功能描述和模块前缀

#define UNDERLINE "_"

示例Good:

#define CMD_SPLIT_CHAR "_"

规则41:避免函数式宏中的临时变量命名污染外部作用域。

说明:首先,尽量减少使用函数式宏。其次,当函数是红需要定义局部变量时,为了防止跟外部函数中的局部变量有命名冲突,可以后置下划线。

示例good:

#define SWAP_INIT(a, b) do { \
    int tmp_ = a;            \
    a = b;                   \
    b = tmp_;                \
} while (0)

4.5.文件/文件夹命名

规则42:文件名文件夹名格式为【模块名 +下划线 +功能描述.后缀】,且统一采用【纯小写】风格。

示例Bad:

init.c
common.h

示例Good:

led_init.c
led_common.h



5.语法规范

5.1.语句

规则43:避免或不使用高难度、高技巧、高复杂性语句。

说明:越简单的代码越容易理解,并且维护成本越小,修改时越不容易引入错误。

5.2.函数

规则44:函数实现应遵循高内聚、低耦合的原理,保证函数功能单一,即:一个函数实现一个功能,不应一个函数参杂太多功能。

5.3.魔鬼数字

规则45:代码中禁止使用魔鬼数字,应该使用能够准确描述其含义的字符串对其进行宏定义。

示例Bad:无法直观看出return 0x11是什么含义,且代码后续会变得难以维护

xxx xxx(xxx) {
    xxx;

    temp = ReadFlashByte(xxx);
    if (temp < 0) {
        xxx;
        return 0x11;
    }

    xxx;
}

示例Good:

#define ERROR_NUM_EMMC_READ_FAILED  0x01
#define ERROR_NUM_FLASH_READ_FAILED 0x02
#define ERROR_NUM_FPGA_READ_FAILED  0x03
...

xxx xxx(xxx) {
    xxx;

    temp = ReadFlashByte(xxx);
    if (temp < 0) {
        xxx;
        return ERROR_NUM_FLASH_READ_FAILED;
    }
}

规则46:宏定义中禁止使用类似于魔鬼数字类型的直译字符串(魔鬼字符串),应该以实际功能命名字符串。具体见《命名规范》章节中《宏命名》规则。

示例Bad:这样的宏毫无意义,后续维护性很差,应该使用数字or字符串的功能含义进行命名,且在前面加上模块前缀。

#define SIXTEEN 16
#define UNDERLINE "_"

示例Good:

#define CMD_PARA_LEN   16
#define CMD_SPLIT_CHAR "_"

5.4.关键字return

规则47:禁止在宏定义中使用return。

说明:因为在宏定义中使用return,会改变代码分支,检查代码时不易发觉代码走向。

5.5.减少全局变量

规则48:全局变量是增大模块间耦合性的原因之一,应尽可能的减少不必要的全局变量。

5.6.关键字extern

规则49:尽量避免或不使用关键字extern,由工具自动生成的代码可以例外。

说明:因为extern声明的方式使用外部函数接口和变量等,容易在外部接口改变时导致声明和定义不一致。同时隐式依赖容易导致架构腐化。后期维护代码进行修改时会导致代码一处修改,处处修改。

例如:假设有100w行代码,其中一个函数接口被1000个.c文件调用,每个.c中均使用extern声明该函数,后期维护修改了该函数定义名称,那么得修改1000处extern声明的地方,架构腐化,牵一发而动全身,代码及难维护。而使用引用头文件#include "xxx.h"的方式,仅需要修改函数定义的地方和xxx.h文件中声明的地方2处即可。

解决办法:

(1) 对于函数:在对应头文件xxx.h中声明,其他所有用到的地方,直接引用xxx.h文件即可。

(2) 对于全局变量:使用static关键字修饰,同时定义两个函数(读/写),来操作该变量:

GetXxxValue();
SetXxxValue();

然后再在对应头文件xxx.h文件中进行声明,当要使用函数的时候,直接include引用头文件xxx.h即可。

5.7.ISO C标准字符

规则50:禁止使用ISO C标准之外的字符。

示例Bad:

void foo () {
    char c = '$'; /* $不属于ISO C标准定义的字符 */
}

5.8.变量初始化

规则51:除了指针、全局变量之外的变量,定义时无需初始化,在使用之前进行初始化即可。

说明:代码中使用变量时还会进行赋值,前面的初始化属于冗余初始化,即:冗余代码。

示例Bad1:

void foo (xxx) {
    int i = 0;
    xxx;
    
    while (i < xxx) {     /* 假如前面忘记初始化,则此处代码逻辑将及易出错,且不易发觉 */
        /* code */
        i++;
    }
}

示例Good1:

void foo () {
    int i;
    xxx;

    i = 0;
    while (i < xxx) {
        /* code */
        i++;
    }
}

示例Bad2:

void foo (xxx) {
    int ret = 0;        /* 冗余初始化 */
    xxx;
    
    ret = GetXxxValue(xxx);
    if (ret < xxx) {
        /* code */
    }
}

示例Good2:

void foo (xxx) {
    int ret;
    xxx;
    
    ret = GetXxxValue(xxx);
    if (ret < xxx) {
        /* code */
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值