# 1.1 前言

C 语言学习之路,道阻且跻,为了不让我自己写代码时轻易迷失在指针和函数的海洋以及提高自己编程的质量和效率,我收集了一些资料,整理归纳并稍作修改,整理出这篇编程规范和注释风格,希望自己能好好遵守,以后写出越来越容易理解和阅读的代码。

操作系统:ubuntu22.04

编辑器:Vim 9.0

编译器:gcc10.3.0

C 语言标准:c11

# 1.2 编码

# 1.2.1 排版

  • 缩进为 4 个空格。
  • 每行不超过 80 列。
  • 文件用UTF-8(without BOM) 编码。
  • 括号采用K&R 风格。
  • 逗号、分号在后面加空格。
  • 比较操作符,赋值操作符、算术操作符、逻辑操作符、位域操作符等双目操作符的前后加空格。
  • 位运算操作符、自增、自减、取地址等单目操作符前后不加空格。
  • ->. 前后不加空格。
  • ifswitchelsefordowhile 等关键字后要加一个空格。
  • 不要在小括号里的表达式两侧加空格。
  • 表达式换行要保持换行的一致性,操作符放行末。

# 1.2.2 命名

  • 变量:全小写,_ 分隔。如 student_score
  • 文件:全小写,不分隔。如 myccode.h
  • 宏:全大写,_分隔。如 API_KEY
  • goto 标签:和宏一致。如 CLEAN
  • 只读变量:全大写,_ 分隔。如:const int DAYS_IN_A_DAY = 7
  • 枚举常数:与常量一致: ENUM_NAME
  • 函数:
    • 动作类函数使用动宾结构:add_user()
    • 判断型函数:is_working()
    • 数据型函数:get_loacal_count()
  • tyepdef 自定义类型:加 _t 后缀。如 time_tpid_t 等。
  • 静态变量:加 _前缀。如 _students_num
  • 全局变量:加 g_前缀。如 g_errno
  • bool 变量:加 is_前缀。如 is_check
  • 指针变量:加 ptr 后缀。如 student_info_ptr
  • 较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写,避免使人产生误解。如 tmpstu_info
  • _ 开头的函数或变量用来命名不希望被外部调用的函数(静态函数)。

# 1.2.3 头文件防卫式声明

#ifndef HEADER_FILENAME_
#define HEADER_FILENAME_

#endif // _HEADER_FILENAME
1
2
3
4

# 1.2.4 函数

  • 不对外使用的函数用 static 限制。
  • 不在函数原型之前使用 extern 关键字。
  • 设计函数时,优先使用返回值而不是输出参数。
  • 对于模块外部传入的参数,必须进行合法性检查,保护程序免遭非法输入数据的破坏。
  • 契约式编程,模块内部函数参数的合法性检查,由调用者负责。
  • 函数的指针参数如果不是用于修改所指向的对象就应该声明为指向 const 的指针。
  • 一个函数仅完成一个功能。
  • 对函数的错误返回码要全面处理。
  • 如果不关心返回值,应将函数声明为 void 类型。
  • 多个 .c 文件要调用同一个内联函数,那么这个内联函数应该在 .h 中定义。
  • 在函数原型中包含参数名和它们的数据类型。
  • 使用 goto 集中函数的退出路径,好处:
    • 无条件语句容易理解和跟踪。
    • 嵌套程度减小。
    • 可以避免由于修改时忘记更新个别的退出点而导致错误。
  • 如果一个函数有 3 行以上,就不要把它变成内联函数。
  • 如果函数的名字是一个动作或者强制性的命令,那么这个函数应该返回错误代码整数。如果是一个判断,那么函数应该返回一个“成功”布尔值。
  • 没有返回值的函数要用 noreturn ,(stdnoreturn.h)修饰。

# 1.2.5 其他

  • 使用 C11 标准。
  • 使用标准数据类型。(stdint.hstdbool.h 内的类型)。
  • 永远不要用 pragma pack
  • 代码中所有无符号整型数字,均应加 U 后缀。如 #define MAX_DEVICES (128U)
  • 每一个.c 文件都应该有对应的 .h 文件,用于声明需要对外公开的接口。
  • 包含多条语句的函数式宏的实现语句必须放在 do-while(0) 中。
  • 不要冗余初始化,对于后续有条件赋值的变量,可以在定义时初始化成默认值,针对大数组的冗余清零,更是会影响到性能。
  • switch 语句要有 default 分支,所有的 switchcase 里都要有 break,如果 default 用于不应该抵达,应使用 assert(false)
  • 使用 goto 集中所有函数出口。
  • 尽量只用数组索引做指针运算,而不是使用指针变量。
  • 整型常量应该用枚举来表示,而不是用宏来定义。
  • 宏函数并没有类型检查,可以考虑用内联函数代替。
  • 头文件中,内联功能启用应用 static inline 来完成。
  • 尽量不用 typedef 给结构体、指针重命名。
  • 操作符两边的数据类型需要一致,以防止隐式类型转换会发生与程序员想法不一致。如:uint8_tint16_t类型的 >,<,= 比较。
  • 包含头文件时使用相对路径,不使用绝对路径。
  • 结构体内含有多个 bool 变量,请考虑将它们合并为具有1比特成员的位域。

# 1.3 注释

之前本来是采用 Doxygen 风格的,但用了一阵子觉得太复杂了,还是简单点好。

错误的注释不但无益反而有害。注释主要阐述代码做了什么(What),或者如果有必要的话,阐述为什么要这么做(Why),注释并不是用来阐述它究竟是如何实现算法(How)的。

# 1.3.1 条件编译

#ednif 后需要加注释。

#if MY_MACRO_NAME
// ...
#endif // MY_MACRO_NAME
1
2
3

# 1.3.2 TODO 注释

对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用 TODO 注释。

例如:

// TODO: 问 ChatGPT,然后粘贴到这里。

// FIXME: 完蛋了,我不知道我删了什么,现在程序出问题了。

// XXX: 这里本来有一个绝佳的算法可以优化,但是时间太短,下次再改。
1
2
3
4
5

# 1.3.3 文件头注释

/******************************************************************************
 *     Copyright:  (C) 2023 shachi
 *                 All rights reserved.
 *
 *      Filename:  temp.c
 *   Description:  This file
 *
 *       Version:  0.1.0
 *        Author:  shachi <shachi1758@outlook.com>
 *     ChangeLog:
 *
 * +------------+----------------------------------------------------+--------+
 * |    Date    |                      Change                        | Author |
 * +------------+----------------------------------------------------+--------+
 * | 2023-05-09 |                   Create file.                     | shachi |
 * +------------+----------------------------------------------------+--------+
 *
 *****************************************************************************/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 1.3.4 函数注释

/**
 *   Description:
 *    Input Args:
 *   Output Args:
 *  Return Value:
 */
1
2
3
4
5
6

# 1.3.5 结构体注释

/**
 * Brief
 */
1
2
3

# 1.3.6 变量注释

int32_t i = 0;  //< 这个是我。
1

# 1.4 参考