构建自己的编码风格 linux C语言

欧大琛 2018-11-15 PM 3998℃ 0条

0.前言

“程序必须为阅读它的人而编写,只是顺便用于机器执行。” ——Harold Abelson 和 Gerald Jay Sussman

良好的编程风格使得代码具有更强的可读性,可维护性,以及可移植性。为了使自己在编码过程中能够保持良好的编程规范,形成自己的风格,我结合多篇著名编程规范文章以及自己工作中的经验,总结提炼,形成以下内容,以备自用与遵循。

1.头文件

原则1.1 头文件中适合放置接口的声明,不适合放置实现。

  • 放置对外的声明:函数声明、宏定义、类型定义;
  • 内部使用的函数、宏、枚举、结构体不应放入;
  • 变量定义应放在.c文件中,尽量不要使用全局变量作为借口,即使必须使用,也应在.c文件中定义,在.h文件中声明(extern);

原则1.2 头文件应当职责单一。

  • 头文件中不应包含其他的头文件,以避免复杂的依赖关系导致文件过大、编译时间过长;

原则1.3 头文件应向稳定的方向包含。

  • 不稳定的头文件依赖于稳定的头文件,即不成熟的头文件包含成熟的头文件,产品依赖于平台,平台依赖于标准库。

规则1.1 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。

  • 程序入口文件除外,如main函数所在文件;
  • 对于内部使用函数,无需在.h中声明,建议使用static在.c文件头部声明, 如:static void fun();

规则1.2 禁止头文件循环依赖。

规则1.3 .c/.h文件禁止包含用不到的头文件。

规则1.4 头文件应当自包含。

  • 不包含其他头文件,能够独立编译;

规则1.5 总是编写内部#include保护符( #define 保护)。

  • 所有头文件都应当使用#define 防止头文件被多重包含。命名格式应为:FILENAME_H,命名唯一,一般以"_"和”__"开头的标识符为系统保留或者标准库使用,自定义中不使用;
#ifndef FILENAME_H
#define FILENAME_H
..
#endif 

规则1.6 禁止在头文件中定义变量。

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

规则1.8 禁止在extern "C"中包含头文件。

2.函数

原则2.1 一个函数仅完成一件功能。

原则2.2 重复代码应该尽可能提炼成函数。

规则2.1 避免函数过长,新增函数不超过50行(非空非注释行) 。

规则2.2 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。

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

  • 可重入函数是指可能被多个任务并发调用的函数,共享变量指的是全局变量和static变量;

规则2.4 对参数的合法性检查

  • 函数参数的校验交由函数自身校验,但此方式在函数嵌套较多时可能会带来校验代码冗余现象,因此对于非对外的函数的参数校验交由最上乘函数校验;

规则2.5 对函数的错误返回码要全面处理。

  • 自写的函数或标准库函数都会有错误返回值,应对错误返回值进行全面的处理,以免程序出错,无法运行;

规则2.6 设计高扇入,合理扇出(小于7)的函数。

  • 扇出是指一个函数直接调用(控制)其它函数的数目;
  • 扇入是指有多少上级函数调用它;

规则2.7 废弃代码(没有被调用的函数和变量)要及时清除。

建议2.1 函数不变参数使用const。

  • 函数中传入的参数不会被重新赋值的建议使用const,以便编译器检查,使代码更加牢固安全。

建议2.2 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。

建议2.3 检查函数所有非参数输入的有效性,如数据文件、公共变量等。

  • 来自文件的数据,寄存器的数据需进行校验以免出错;

建议2.4 函数的参数个数不超过10个。

建议2.5 除打印类函数外,不要使用可变长参函数。

建议2.6 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。

3.标识符命名与定义

3.1 通用命名规则

  • 无论变量还是函数,单词用小写字母,每个单词使用下划线“_”分割,如quit_sig,pthread_mutex_t,pthread_mutex_lock();;
  • 对于全局变量应添加前缀 “g_”,如 g_number_of_cards;
  • 对于局部静态变量,应添加前缀 “s_”,如 s_number_of_people;
  • 对于宏的命名一律采用大写字母,并用“_”分割,如EXAMPLE_UNIT_TEST;
  • 对于没枚举、和结构体的联合体等复合型的结构,在命名后添加后缀“_t”,如uint32_t,config_t;

原则3.1 标识符命名应该清晰明了,除通用缩写外,不使用用自定的缩写,不使用拼音。

  • 通用缩写如下:
单词缩写
argumentarg
argumentarg
bufferbuff
clockclk
commandcmd
comparecmp
configurationcfg
devicedev
errorerr
hexadecimalhex
incrementinc
initializeinit
maximummax
messagemsg
minimummin
parameterpara
previousprev
registerreg
semaphoresem
statisticstat
synchronizesync
temptmp

建议3.1 使用正确的反义词组,与互斥名词、动词。

  • 常见互斥词如下:
123
add/removebegin/endcreate/destroy
insert/deletefirst/lastget/release
increment/decrementput/getadd/delete
lock/unlockopen/closemin/max
old/newstart/stopnext/previous
source/targetshow/hidesend/receive
source/destinationcopy/pasteup/down

建议3.2 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。

3.2 文件命名规则

建议3.3 文件命名统一采用小写字符,单词之间使用下划线区分“_”。

3.3 变量命名规则

规则3.2 全局变量应该增加前缀“g_”。
规则3.3 静态变量应该增加前缀“s_”。
规则3.4 变量命名必须具有具体含义,但是对于局部循环变量,允许使用i,j,k,l,m,n。

3.4 函数命名规则

建议3.4 函数名采用小写字母,字母使用“_”分割。

3.5 宏的命名规则

规则3.5 宏以及枚举均使用全大写字母,以“_”分割单词。

规则3.6 除了头文件及宏开关,其他宏不可以“_”开头和结尾。

4.变量

原则4.1 一个变量只有一个功能。

原则4.2 结构功能单一。

  • 设计数据结构时,不应一个结构中包含其他不相干内容

原则4.3 不用或者少用全局变量。

规则4.1 防止局部变量与全局变量同名。

规则4.2 使用结构时需要注意字节顺序。

规则4.3 严禁使用未经初始化的变量作为右值。

5.宏、常量

规则5.1 用宏定义表达式时,要使用完备的括号。

  • 示例:如下定义的宏都存在一定的风险
#define RECTANGLE_AREA(a, b) a * b
#define RECTANGLE_AREA(a, b) (a * b)
#define RECTANGLE_AREA(a, b) (a) * (b)

正确的定义应为:

#define RECTANGLE_AREA(a, b) ((a) * (b))

规则5.2 将宏所定义的多条表达式放在do-while(0) 的大括号中。
-如下宏:

#define FOO(x) \
printf("arg is %d\n", x); \
do_something_useful(x); 

若如下调用

eg1:
for (i = 0; i < 10; i++)
    FOO(5);

eg2:
if (condition == true) {
    FOO(1);
} else {
    FOO(2);
}

则此时都会照成语法错误,但是使用do-while(0)方式就不会出现以上问题

#define FOO(x) do {\
printf("arg is %d\n", x); \
do_something_useful(x); \
} while (0)

规则5.3 使用宏时,不允许参数发生变化。

  • 使用宏是参数不建议使用 ++、--等运算符,以免宏计算出的值偏离预期;

规则5.4 不允许直接使用魔鬼数字。

  • 即不使用无意义的数字,以免阅读者无法理解该数字含义;
  • 非必要时,不使用宏定义常量而使用const定义,预编译后宏定义的常量可能会变成魔鬼数字,不好定位调试。

6.注释

规则6.1 注释语法按Doxygen语法要求,以便使用该软件生成api文档
规则6.2 注释不是名词解释,而是说明用途
规则6.3 文件头注释

  • 对于.c、.h、.asm文件,都必须在文件的开头对文件整体功能进行注释,注释使用模板如下:
/*!
 *****************************************************************************
 *
 *  Copyright © 2017-2018 Gustav Ou. All rights reserved.
 *
 * \file    sample.c(可不写,软件会自动补充)
 * \author  Gustav
 * \version 1.0
 * \date    2017/10/22
 * \brief  这是一个示例文件,向你展示文件头的模板
 *       具体描述
 *
 *----------------------------------------------------------------------------
 * \attention
 *  这仅是一个示例文件
 *
 *****************************************************************************
 * change history:
 *  <date>      | <version> | <author>   | <discription>
 *----------------------------------------------------------------------------
 * 2017/10/22   | 1.0       | Gustav     | creat file
 *----------------------------------------------------------------------------
 *
 *****************************************************************************
 */

规则6.4 函数头注释

  • 函数头注释仅注释与函数声明前,如.h中的函数声明,.c中的内部函数声明
  • 函数头模板(参数接收应上下对其,参数可选项使用中括号括起较多时可新起一行,参数应使用[in], [out], [in,out]
    表示其方向):
/*!
 * \brief 函数头测试模板
 *        详细描述(根据需要,以下跟一个空行表示结束)
 *
 * \param [in] param1     参数一解释
 *                        [0: 高级,1:中级,2: 低级,其他: 无级别 ]
 * \param [out] param2    参数二解释
 * \param [in] param3     参数三解释 [1: 有效,2: 无效]
 *
 * \retval returnvale     返回值的解释 [1: 成功,2: 失败](void无必要)
 */

规则6.5 注释块

  • 详细注释
/*!
 * ... text ...
 */ 
  • 繁简并存的注释
/*! \brief Brief description.
 * Brief description continued.
 * 
 * Detailed description starts here.
 */
  • 注释拆成单行
//! Brief description.
//! Detailed description
//! starts here.

或者

//! Brief description, which is
//! really a detailed description since it spans multiple lines.
/*! Another detailed description!
*/ 

规则6.6 成员注释

  • 如果需要在文件的变量,结构体、联合体、类、枚举等的成员添加注释,需要使用<标记
int var; /*!< Detailed description after the member */ 
//!或
int var; //!< Brief description after the member 

排版

规则7.1 程序块采用缩进风格编写, 每级缩进为4个空格。

规则7.2 相对独立的程序块之间、变量说明之后必须加空行。

规则7.3 一条语句不能过长,如不能拆分需要分行写,一行132列以内。

规则7.4 起始大括号放置在关键字(if,switch,for,while,do)同一行,结束大括号新起一行。

if (x is true) {
    we do y
}

switch (action) {
case KOBJ_ADD:
    return "add";
case KOBJ_REMOVE:
    return "remove";
case KOBJ_CHANGE:
    return "change";
default:
    return NULL;
}

do {
    body of do-loop
} while (condition);

if (x == y) {
    ..
} else if (x > y) {
    ...
} else {
    ....
}

规则7.5 大多数关键字、二元、三元运算符加空格

//!关键字后添加空格
if, switch, case, for, do, while 

//!函数或类函数不加空格
s = sizeof(struct file);
//!二元、三元运算符后添加空格
=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :
//!一元运算符后不加空格
&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined
//!前后缀自家自减运算符后不加空格
++  --
//!成员操作符后不加空格
. ->
//!逗号后添加空格
,

8.打印

  • log打印分为多种情况,调试用打印,错误提示,警告提示等,建议将其独立成头文件引用:
#define DEBUG
#define ERROR
#define WARNING
#define INFO
#define NONE_MSG

#ifdef DEBUG
//#define DEBUG_MSG(format, args...) printf("DEBUG(%s;%d;%s): "format"\n", __FILE__, __LINE__, __FUNCTION__, ##args)
#define DEBUG_MSG(format, args...) printf("DEBUG(%s;%d): "format"\n", __FILE__, __LINE__, ##args)

#else
#define DEBUG_MSG(format, args...)
#endif

#ifdef ERROR
#define ERROR_MSG(format, args...) printf("ERROR: "format"\n", ##args)
#else
#define ERROR_MSG(format, args...)
#endif

#ifdef WARNING
#define WARNING_MSG(format, args...) printf("WARNING: "format"\n", ##args)
#else
#define WARNING_MSG(format, args...)
#endif

#ifdef INFO
#define INFO_MSG(format, args...) printf("INFO: "format"\n", ##args)
#else
#define INFO_MSG(format, args...)
#endif

#ifdef NONE_MSG
#define MSG(args...) printf(args)
#else
#define MSG(args...)
#endif

参考

华为技术有限公司C语言编程规范
linux内核代码风格

提示

可能有人会觉得注释部分在实际编程中过于繁琐,若按照文中要求,可能会大大影响编程效率,不过不用担心,重复的事情我们总可以交给计算机来做,后续文章将会介绍如何使用SourceInsight的宏编程,来快速添加注释,生成文件头等。

标签: Linux, 编码风格

「一键投喂 软糖/蛋糕/布丁/牛奶/冰阔乐!」

欧大琛

(๑>ڡ<)☆谢谢老板~

使用微信扫描二维码完成赞赏

非特殊说明,本博所有文章均为博主原创。

评论啦~