我的C编程规范

前言

  如标题,这是我个人使用的C编程规范,仅在编写个人项目代码时使用,如有需要可以参考。创建或维护其他项目代码时,必须遵循这些项目指定的编程规范,即使它并不符合你的习惯。此文根据我的个人习惯而撰写,同时参考了其他的规范进行必要补充,参考的文章有:

  1、【务必收藏】嵌入式C编码规范

  2、华为C语言编程规范(精华总结)

术语和约定

下划线命名法: 全小写,名称中的每一个逻辑断点都用一个下划线来标记,例如:print_price。(下划线命名法是随着C语言的出现流行起来的,在UNIX/LIUNX这样的环境,以及GNU代码中使用非常普遍。)

小驼峰命名法: 除第一个单词之外,其他单词首字母大写,例如:myFirstName。常用于变量名,函数名。

大驼峰命名法(又称为帕斯卡命名法): 相比小驼峰法,大驼峰法把第一个单词的首字母也大写了,例如:DataBaseUser。常用于类名,属性,命名空间等。

匈牙利命名法: 其基本原则是,变量名=属性+类型+对象描述。通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域,类型等。这些符号可以多个同时使用,顺序是先m_(成员变量),再指针,再简单数据类型,再其他。例如:m_lpsStr,表示指向一个字符串的长指针成员变量。

  本规范混合使用了以上命名法。

规范

排版与格式

  • 不使用制表符(Tab键),每个级别缩进使用4个空格
int main(void)
{
    printf("你好,世界");    //使用4个空格缩进而不是制表符缩进
    return 0;
}

原因:不同编辑器对制表符缩进量有不同定义,因此,使用不同的编辑器打开源代码可能会因缩进量不合适而导致代码不美观,降低代码可读性。不过一般编辑器都可以自定义制表符(Tab键)为多空格。

  • 关键字与左括号之间使用一个空格,函数名和左括号之间不要使用空格
void delay(uint32_t xms)    //函数名和左括号之间不要使用空格
{
	while (xms--) {         //关键字与左括号之间使用一个空格
		nop();
	}
}

原因:区别关键字和函数名。

  • if 、for 、do 、while 、case 、switch 、default 等语句独占一行

  • 左花括号总是与关键字(for, while, do, switch, if,…)在同一行,且左花括号前面有单个空格

if (x < 100) {              //左花括号与关键字在同一行,且左花括号前面有单个空格
    x += 1;
}

原因:使代码更加紧凑,提高可读性,并区别于函数。

  • 多条语句之间使用单空格

  • 在双目运算符之前和之后使用单个空格

for (a = 0; a < 5; ++a) {   /* OK */
    a = 1 + 1;              /* OK */
}
for (a=0;a<5;++a) {         /* Wrong */
    a=1+1;                  /* Wrong */
}

原因:简明美观,且不容易导致词法分析器构词错误,词法分析器使用贪心算法——即尽可能构造更长的有意义符号,这在某些情况下可能会偏离设计者的原意图。

  • 每个逗号后用单空格
func_name(5, 4);            //每个逗号后用单空格

原因:直观地分隔多个参数或表达式。

  • ifelse后面必须使用复合语句,即使它只包含一个语句
/* OK */
if (c) {
    do_a();
} else {
    do_b();
}

/* Wrong */
if (c)
    do_a();
else
    do_b();

原因:如果不使用复合语句,之后若需要在if或else里面添加语句(如调试打印),但遗漏了花括号,会导致只有第一条语句与if或else结合。

  • if-elseif-else-if语句的情况下,else必须与前一条if复合语句的右括号在同一行
/* OK */
if (a) {

} else if (b) {

} else {

}

/* Wrong */
if (a) {

}
else {

}

原因:使代码更加紧凑,提高可读性。

  • do-while语句的情况下,while部分必须与do部分的右括号在同一行
do {
    int32_t a;
    a = do_a();
    do_b(a);
} while (check());          /* while部分必须与do部分的右括号在同一行 */
  • 每一个嵌套的复合语句都需要添加单个缩进
if (a) {
    do_a();
} else {
    do_b();
    if (c) {
        do_c();
    }
}
  • 为每个case语句的代码块添加单个缩进
/* OK */
switch (check()) {
    case 0:
        do_a();
        break;
    case 1:
        do_b();
        break;
    default:
        break;
}

/* Wrong */
switch (check()) {
    case 0:
    do_a();
    break;
    case 1:
    do_b();
    break;
    default:
    break;
}
  • 总是包含default语句

  • 一条语句不能过长,语句太长要分行

  • 换行时要增加一级缩进,使代码可读性更好

  • 在低优先级操作符处划分新行,换行时操作符应该也放下来,放在新行首

  • 换行时建议一个完整的语句放在一行,不要根据字符数断行

if ((temp_flag_var == TEST_FLAG)
    &&(((temp_counter_var - TEST_COUNT_BEGIN) % TEST_COUNT_MODULE) >= TEST_COUNT_THRESHOLD))
{
    // process code
}

原因:一行代码应该在屏幕显示范围内全部显示,方便阅读。

  • 每条语句尽量独占一行

  • 注释符(包括/**/、//)与注释内容之间要用一个空格进行分隔

  • 为提高可读性,在函数中使用空行分隔概念上的多个部分代码

命名规则

  • 除了头文件或编译开关等特殊标识定义,不要定义由下划线_和双下划线__开头的标识符
#ifndef __LED_H
#define __LED_H             /* OK */
#endif

int __count;                /* Wrong */

原因:这是为C语言本身保留的,避免与标准库及操作系中保留的标识符冲突。

  • 命名时要使用有意义的标识符

原因:使用有意义的标识符比使用注释说明要更直观,更省时省脑。

  • 标识符长度不得超过63个字符

原因:虽然现在很多的编译器已经支持更长的标识符了,但是为了避免在不同编译器中因标识符太长而被截断,从而引起同名歧义,还是尽量不要使用太长的标识符。

  • 使用完整的单词,除非是约定成俗的,否则禁止使用英文缩写

原因:使用英文缩写可能会导致歧义,为避免使人产生误解,应使用完整的单词。

  • 常见缩写
argument (实参) 可缩写为 arg
parameter (形参) 可缩写为 param
buffer (缓冲/缓存) 可缩写为 buff
clock (时钟) 可缩写为 clk
command (命令) 可缩写为 cmd
compare (比较) 可缩写为 cmp
configuration (配置) 可缩写为 cfg
device (设备) 可缩写为 dev
error (错误) 可缩写为 err
hexadecimal (十六进制) 可缩写为 hex
initialize (初始化) 可缩写为 init
maximum (最大) 可缩写为 max
minimum (最小) 可缩写为 min
message (消息) 可缩写为 msg
previous (先前的) 可缩写为 prev
register (寄存器) 可缩写为 reg 
temp (临时的) 可缩写为 tmp
  • 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等
add/remove 
begin/end 
create/destroy
insert/delete 
first/last 
get/release
put/get 
add/delete
lock/unlock 
open/close 
min/max
old/new 
start/stop  
next/previous
send/receive
source/destination 
up/down
  • 文件命名统一采用小写字符

原因:因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。

  • 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构

  • 函数名采用下划线命名法,单词必须小写,多个单词之间用下划线_分隔

int8_t get_motor_status(void);
  • 使用名词或者形容词+名词方式命名变量

  • 变量名采用小驼峰命名法,除第一个单词之外,其他单词首字母大写

int32_t myCount;            /* OK */
int32_t YourCount;          /* Wrong */
int32_t hiscount;           /* Wrong */
int32_t her_count;          /* Wrong */
  • 结构名采用大驼峰命名法
  • 结构成员命名遵循变量命名规则
  • 使用typedef声明结构时,新类型名后面必须包含_t后缀
  • 结构标签名后不能包含_t后缀
typedef struct TagName {    /* 结构标签名后不能包含_t后缀 */
    char* myEye;            /* 结构成员命名遵循变量命名规则 */
    char myEar;
} TypeName_t;               /* 新类型名后面必须包含_t后缀 */

原因:_t后缀表示这是一个类型名,标签名不是类型名,所以标签名不包含_t后缀,以区别其是一个标签。

  • 枚举名采用大驼峰命名法
  • 枚举成员命名必须大写
  • 使用typedef声明枚举时,新类型名前面必须包含e前缀
  • 枚举标签名前面不能包含e前缀
typedef enum BitAction { 
    BIT_RESET = 0,
    BIT_SET
} eBitAction;

原因:e前缀表示这是一个枚举类型名,标签名不是类型名,所以标签名不包含e前缀,以区别其是一个标签。

  • 全局变量应增加g_前缀,静态变量应增加s_前缀

原因:全局变量和静态变量十分危险,特别是在多线程中,通过前缀使得全局变量和静态变量更加醒目,促使开发人员对这些变量的使用更加小心。增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。

  • 禁止使用单字节命名变量,但允许定义i 、j、k作为局部循环变量

变量的命名要清晰、明了,有明确含义.

  • 防止局部变量与全局变量同名

原因:尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。

变量声明与初始化

  • 总是在块的开头声明局部变量,在第一个可执行语句之前

原因:虽然多数编译器支持在使用前才声明变量,但某些编译器仅支持在块开头声明变量,为提高代码的可移植性,应遵守此条准则。

  • 在for循环中声明计数器变量
for (int i = 0; i < 10; ++i) {    //在for循环中声明计数器变量
	x = i * 2;
}

原因:在for循环中声明的计数器变量其作用域为块作用域(for语句块),在for循环体结束后变量所占内存就得以自动释放。

  • 按顺序声明变量
      ① 自定义结构和枚举
      ② 整数类型,更宽的无符号类型优先
      ③ 单/双浮点

  • 按类型将未初始化的局部变量分组在一起声明

int32_t a, b;               /* OK */
char a;
char b;                     /* Wrong, char type already exists */
  • 不要在同一行中声明不同类型的变量
void func(void) {
    char a, b;              /* OK */
    int *c, d;              /* Wrong */
}

原因:特别地,如果在声明指针类型变量的同时声明整数类型变量,容易让人误认为后者也是指针类型变量。

  • 每个初始化局部变量单独占一行
int32_t a, b = 2;           /* Wrong */
int32_t a = 1;              /* OK */
int32_t b = 2;              /* OK */

原因:初始化和未初始化的局部变量放在一起,容易让人误认为未初始化的局部变量也被初始化为跟初始化局部变量一样的值。

  • 不要初始化静态和全局变量为0,让编译器为您做
static int32_t a;           /* OK */
static int32_t b = 4;       /* OK */
static int32_t c = 0;       /* Wrong */

原因:未初始化的静态和全局变量位于.bss段,在main函数运行前会被统一初始化为0。初始化的静态和全局变量位于.data段,初始化值从可执行代码(.text段)中加载,因此需要额外的ROM开销。

  • 避免在声明中使用函数调用来赋值变量
int32_t a, b = sum(1, 2);   //避免在声明中使用函数调用来赋值变量
int main(void)
{
	a = b * b;
    return 0;
}

原因:特别是在声明中使用函数调用来赋值全局变量,全局变量需要使用常量来初始化,而不是使用运行时值来初始化。全局变量的初始值在main函数执行前就会被加载,通常此时还不具备C代码的执行环境。再者,函数必须在函数体中被调用,不能在函数体以外的任何位置执行任何语句,通常在函数体外只有声明(声明并非语句,语句只有六种,详见https://blog.csdn.net/weixin_44567318/article/details/117322765,搜索“语句”)。

  • 在声明中初始化结构时,使用C99初始化风格,即.成员名 = 值
/* OK */
StructName_t var = {
    .a = 4,
    .b = 5,
};

/* Wrong */
StructName_t var = {1, 2};
  • 除了char、float或double之外,始终使用stdint.h标准库中声明的类型。例如,8位的uint8_t等
uint8_t status;             /* OK */
int32_t num;                /* OK */
int count;                  /* Wrong */

原因:stdint.h标准库中声明了具有精确位宽的整型类型,这些类型的位宽在不同编译器和硬件平台下都是精确的,这由C99标准保证。因此,使用这些类型可以提高代码移植性。

变量规则

  • 一个变量只有一个功能,不能把一个变量用作多种用途

原因:把一个变量用作多种用途会给程序分析和理解带来困难,也更容易出错。

  • 结构功能单一,不要设计面面俱到的数据结构

相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象。

  • 不用或者少用全局变量

原因:使用全局变量容易出现无意间的数据修改,特别是在多线程和中断环境下,对全局变量的非原子修改容易导致数据一致性问题。同时,使用全局变量的函数需要经过特殊处理才能实现可重入,比如对全局变量上锁。

  • 严禁使用未经初始化的变量作为右值

原因:特别是局部变量,未初始化时其值是不确定的。应该在首次使用前初始化变量,初始化的地方离使用的地方越近越好。

函数规则

  • main函数使用C99标准的两种标准形式之一
    无参形式:int main(void)
    有参形式:int main(int argc, char *argv[])

  • main函数建议放在开头

  • 不提供外部访问的函数,应该使用static修饰

原因:多人合作时难免会出现函数命名同名的情况,使用static将函数的链接限定在本文件内(内部链接),在其他文件中不可见,这样可以最大限度地避免冲突。但是,如果在同一翻译单元中同名函数同时具有外部链接和内部链接时,行为是未定义的。因此函数的命名应该保证唯一性,具有外部链接的函数绝不能跟任何其他函数同名,即使其他函数由static修饰。

  • 提供给外部访问的函数,必须在相应的头文件中包含函数原型(或声明)

原因:这样在使用这些函数时,只需直接包含相应的头文件即可,无需另写声明,而且方便使用时查阅。

  • 不提供外部访问的函数,在相应的头文件中不包含函数原型(或声明)

  • 对齐所有的函数原型以提高可读性

/* OK, function names aligned */
void        set(int32_t a);
my_type_t   get(void);
my_ptr_t*   get_ptr(void);

/* Wrong */
void set(int32_t a);
const char * get(void);
  • 一个函数仅实现一个功能

原因:一个函数实现多个功能给开发、使用、维护都带来很大的困难,会导致函数职责不明确,难以理解,难以测试和改动。

  • 重复代码应该尽可能提炼成函数

原因:一个函数实现多个功能给开发、使用、维护都带来很大的困难,会导致函数职责不明确,难以理解,难以测试和改动。

  • 避免函数过长,函数不超过 50 行

原因:函数过长会影响阅读和理解。

  • 函数的参数个数不超过5个

原因:函数参数过多时会显得混乱,语句也会太长,降低代码可读性。

  • 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护

可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

  • 对参数进行合法性检查
  • 检查函数所有非参数输入的有效性,如全局变量、数据文件等

原因:遗漏合法性检查可能会导致隐患。

  • 对函数的错误返回码要全面处理

原因:忽略某些错误可能是致命的。

表达式规则

  • 尽量避免使用字面量,使用常量标识符和宏来表示字面量,特别是数值
if (status = NET_CONNECTED) {   /* OK */
	level = HIGH_LEVEL;         /* OK */
}
if (status = 1) {               /* Wrong */
	level = 1;                  /* Wrong */
}
  • 使用指针前,总是将指针与NULL进行比较,不要使用0来替代NULL
if (ptr != NULL)

原因:对NULL指针指向的内存进行操作,可能会出现意想不到的问题。因此,在使用指针前应该首先检查其值是否为NULL。C99标准规定NULL的值为实现定义的空指针常量,虽然大多数实现定义NULL的值为0,但是为了提高代码移植性,应该使用NULL而不是0。

  • 总是使用前增量(和递减),而不是后增量(和递减)
a++;                        /* Wrong */
++a;                        /* OK */

原因:通常情况下前增量比后增量的执行效率要高,后增量在没有优化的情况下可能会多一个副本值(自增前的值),占用寄存器资源。而且如果a=0,表达式++a的值是自增后的值,即1,而表达式a++的值是自增前的值,即0。表达式a++会容易让人误认为是自增后的值,即1。

  • 总是使用size_t作为长度或大小变量
void func(void *buff, size_t len)

原因:size_t是sizeof运算符的返回值类型,也是内存分配函数malloc等函数的分配内存长度形参类型,也即是说,size_t类型大小是我们调用一次内存分配函数所能分配到的最大内存大小(通常大于或等于实际可用内存大小)。所以,使用size_t类型可以提高不同内存大小机器间的移植性。

  • 如果函数不应该修改形参或变量,则总是使用const限定符来修饰这些形参和变量
void func(const size_t len)
void func(const uint8_t *data)      //常量指针
void func(uint8_t const *data)      //常量指针
void func(uint8_t *const data)      //指针常量

原因:防止意外的修改,对const限定符修饰的变量进行修改,编译器会报错。同时也便于区分入参和出参。
注意:const修饰指针变量时,不要混淆指针常量和常量指针。

  • 当函数可以接受任何类型的指针时,总是使用void *。函数在实现时必须注意正确的类型转换
void func(void *data)
{
	uint32_t *ptr;
	ptr = (uint32_t *)data;
}

原因:void *更符合习惯且非常直观地表示可以接受任何类型的指针,而使用其他指针类型如uint8_t *等会起到误导作用。

  • 赋值语句不要写在条件表达式
if ((a == 0) || ((b = fun1()) > 10))    /* Wrong */

原因:如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。

头文件规则

  • 头文件必须包含保护符#ifndef,以防止重复引入头文件
#ifndef __LED_H
#define __LED_H

#endif
  • 在头文件中总是包含带有extern关键字的c++检查
#ifdef __cplusplus
extern "C" {
#endif

void delay(uint32_t xus);

#ifdef __cplusplus
}
#endif

原因:避免在C++环境下调用C预编译库时,因C和C++对函数的名称修饰规则不同而找不到函数。

  • 不要在extern "C"里面包含头文件
#include "money.h"          /* OK */

#ifdef __cplusplus
extern "C" {
#endif

#include "car.h"            /* Wrong */

#ifdef __cplusplus
}
#endif

原因:避免extern "C"的嵌套,嵌套太深可能会引起编译器报错。并且这可能会无意间改变函数的链接规范,如,本应以C++规范链接的函数func,被头文件包含在extern "C"里面,则func函数会以C链接规范进行链接。

  • 对于C标准库头文件,请始终使用<和>。例如,#include <stdlib.h>
  • 对于自定义库,请始终使用""。例如,#include "my_library.h"

原因:减少头文件的搜索时间,同时避免在标准路径和当前目录下存在同名头文件时,引用错误路径下的头文件。

  • 头文件必须包含它所依赖的其他所有头文件(自包含),以便正确编译,但不能包含更多头文件(如果需要,.c应该包含其余的头文件)

原因:头文件不包含它所依赖的其他头文件,那么源文件中就要在包含该头文件之前,包含其他头文件,这就产生了包含顺序的依赖。头文件中包含太多不必要的其他头文件,会导致文件编译速度慢。

  • 在文件末尾输入一个空行,之后不要再输入任何其他字符(包括空白)
void func(void)
{
	printf("Nice!");
}

原因:这是C99标准规定,虽然某些编译器不保留空行也没问题,但在某些编译器下会导致警告。之所以这样规定,跟Unix系统对文件流的处理有关。

  • 头文件示例
/* License comes here */
#ifndef TEMPLATE_HDR_H
#define TEMPLATE_HDR_H

/* Include headers */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

/* File content here */

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* TEMPLATE_HDR_H */

  • 头文件中只放置接口的声明,不放置接口的实现

原因:这容易造成函数或变量的重复定义。

宏规则

  • 所有的宏必须全大写,多个单词之间使用下划线_分隔
  • 总是用圆括号保护输入参数
  • 总是用圆括号保护最终的宏计算
/* OK */
#define MULTIPLY(x, y)           ((x) * (y))

/* Wrong */
#define MULTIPLY(x, y)           (x * y)

/* Wrong */
#define MULTIPLY(x, y)           (x) * (y)

/* Wrong */
#define MULTIPLY(x, y)           x * y

原因:使用圆括号以避免宏在展开时,因运算优先级不同导致错误的结合。

  • 当宏使用多个语句时,使用do-while(0)语句保护它
#define HANDLE(a, b)  do {\
	a += b;\
	b *= b;\
} while (0)

原因:如果多个语句不使用花括号保护,会导致if、for、while等语句在缺失花括号时,只与第一个语句结合。如果单纯使用花括号保护多个语句,则不能按照习惯在宏后面使用分号,否则可能会因在if和else之间多出一个分号而报错。详见以下代码:

#define HANDLE(a, b)  	a += b; b *= b

if (c)
	HANDLE(a, b);
else
	other_handle(a, b);
	
/* 宏展开后 */
if (c)
	a += b;       
	b *= b;                 /* 缺少花括号,这里必须是复合语句 */  
else
	other_handle(a, b);
#define HANDLE(a, b)  	{a += b; b *= b;}

if (c)
	HANDLE(a, b);
else
	other_handle(a, b);
	
/* 宏展开后 */
if (c) {
	a += b; 
	b *= b;        
};                          /* 多了一个分号 */
else
	other_handle(a, b);

注释规则

  • 注释时使用12个缩进(12 * 4个空格)偏移量。如果语句大于12个缩进,将注释对齐到下一个可用缩进
void func(void) {
    char a, b;
    a = call_func_returning_char_a(a);          /* 注释时使用12个缩进(12 * 4个空格)偏移量 */
    b = call_func_returning_char_a_but_func_name_is_very_long(a);   /* 如果语句大于12个缩进,将注释对齐到下一个可用缩进 */
}
  • 注释应放在其代码上方相邻位置或右方,如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同
/* active statistic task number */
#define MAX_ACT_TASK_NUMBER 1000
#define MAX_ACT_TASK_NUMBER 1000     /* active statistic task number */
   
/* 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*/
};
  • 多行注释时,每行都以单个空格开头,然后是一个星号,在第二个缩进处开始注释内容
/*
 *  多行注释时,每行都以两个空格开头,
 *  然后是一个星号,
 *  在第二个缩进处开始注释内容
 */
  • 尽量让代码本身可以自我解释,不通过注释即可轻易读懂

请比较下面具有同样功能的代码

/* 判断m是否为素数*/
/* 返回值:: 是素数,: 不是素数*/
int p(int m)
{
    int k = sqrt(m);
    for (int i = 2; i <= k; i++)
        if (m % i == 0)
            break; /* 发现整除,表示m不为素数,结束遍历*/
    /* 遍历中没有发现整除的情况,返回*/
    if (i > k)
        return 1;
    /* 遍历中没有发现整除的情况,返回*/
    else
        return 0;
}
int IsPrimeNumber(int num)
{
    int sqrt_of_num = sqrt(num);
    for (int i = 2; i <= sqrt_of_num; i++)
    {
        if (num % i == 0)
        {
            return FALSE;
        }
    }
    return TRUE;
}
  • 注释的内容要清楚、明了,含义准确,防止注释二义性
  • 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图 , 而不是重复描述代码
  • 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写
  • 注释使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达
  • 对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释
case CMD_FWD:
    ProcessFwd();
    /* now jump into c ase CMD_A */
case CMD_A:
    ProcessA();

原因:这样比较清楚程序编写者的意图,不会令人误以为是遗漏了break语句,有效防止无故遗漏break语句。

文件规则

  • 文件应当职责单一,切忌依赖复杂
  • 每一个 .c 文件应有一个同名 .h 文件,用于声明需要对外公开的接口
  • 头文件应向稳定的方向包含,应当让不稳定的模块依赖稳定的模块

原因:不稳定的模块发生变化时,不会影响(编译)稳定的模块

  • 禁止头文件循环依赖

原因:如果a.h包含b.h,b.h包含c.h,c.h包含a.h,任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。一些严谨的编译器会对此类情况报错。

  • .c/.h文件禁止包含用不到的头文件

原因:这会导致整个系统的编译时间进一步恶化,并对后来人的维护造成巨大的麻烦。

  • 只能通过包含头文件的方式使用其他 .c 提供的接口,禁止在.c 中通过 extern 的方式使用外部函数接口、变量

原因:这种写法容易在接口改变时可能导致声明和定义不一致,而且不能做到一改全改。

  • 一个模块包含多个 .c 文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议模块提供一个 .h 文件,文件名为目录名

注意:这个.h并不是简单的包含所有内部的.h,它是为了模块使用者的方便,对外整体提供的模块接口。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值