C 语言编程约定

此文为约定,并非完全遵守某个规范,而是由自己的编程习惯总结而来。

文件字符编码

一切的前提。

强烈建议使用 UTF-8 编码格式。UTF-8 编码已经广泛应用在 WEB 中,是首选的编码格式。

UTF-8 编码是 Unicode 标准中的变长编码方式,可以表示 Unicode 中任意一个字符,使用1~4个字节表示字符,且字节顺序无关。统一使用 UTF-8 字符编码可以避免各种乱码问题的出现。

但是使用 UTF-8 编码格式的目的并不是为了解决乱码问题,而是为了格式的统一,避免在国际化过程中出现乱码而已。另外,不建议在代码里使用中文(除非是协议特殊需要传输非 UTF-8 中文字符的情况)。

在使用各种 IDE 或者文本编辑器的时候,请留意字符编码格式设置,修改成 UTF-8。

中文问题

结论:不建议程序里使用中文,注释也不建议使用中文,独立的文档可以是中文。

原因:

  1. 文件国际化格式过程中非常容易出现乱码,将乱码转成正常的过程麻烦且耗时,无端增加工作量;
  2. IDE 支持不友好,比如 keil MDK;
  3. 英文依旧是程序设计的主要语言;

缩进与 Tab 问题

在嵌入式 C 编程里,只用 4 个空格进行缩进,杜绝使用 Tab

由于 TAB 控制符在不同的地方其展示宽度不同,尤其是当 TAB 和空格混用的时候,导致代码乱糟糟的。

建议:

拒绝使用 TAB,使用 4 个空格代替,并使用代码格式化工具检查。

文末空行

程序文件末尾必须留一行空行。

因为某些编译器会报警告(如 keil)。

多余空格和换行问题

  • 每行末尾不留多余的空格
  • 空行里不要有空格
  • 不要留多行空行(不超过 2 行空行)

建议每一小块代码前后空一行。

不建议的 示例代码:

void main(void)  /* 这里多留了空格 */
{  /* 这里多留了空格 */
    while(1)
    {
        /* TODO */
    }



    /* 这里空了很多行 */
}

回车换行符问题

请使用 CRLF 也就是 \r\n 进行换行,不要使用独立的 LF

函数、变量、文件的命名约定

  • 命名风格

    个人喜欢 Unix 风格,使用全小写字母加 _ 命令,简单直观。

  • 全局变量命名

    关于全局变量,虽不建议全局变量满天飞,但偶尔还是需要定义全局变量的,我习惯将全局变量名以前缀 g_ 开头。

    g_ 其实是匈牙利命名法里的,这里沿用的目的是更加醒目地提示变量为全局变量,用的时候需要留意。

  • 枚举类型命名

    枚举元素名全大写,枚举名也建议全大小,枚举元素必须显示给出明确值。

  • typedef 重命名

    typedef 重命名使用全小写格式,并以 _t 后缀结尾。

  • 宏定义全部大写

  • 文件、文件夹命名请全部使用小写字母加 _ 的方式。

文件头注释

一个文件的文件头部应该给出文件名、许可信息、更新历史等内容。我常用的格式如下所示:

/*
 * Copyright (c) 2021, <这里替换你的邮箱或者公司的名称>
 *
 * <这里替换成使用的许可协议>
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-01-01     xxx          first version
 */

头文件基本格式

为了避免头文件被重复包含导致各种重定义问题或其他问题,通常头文件格式如下:

#ifndef __FILE_H__
#define __FILE_H__

/* 其他内容 */

#endif

通过定义一个宏来避免文件被重复包含。宏的名称需注意格式为全大写,并且是将文件名全大写并前后加两个 _ 转变而来。

C/C++ 兼容

头文件里按照如下方式进行 C/C++ 兼容。

#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */

/* 这里放数据结构和函数原型 */

#if defined(__cplusplus)
}
#endif /* __cplusplus */

80 线

可以考虑将每行的字符数量控制在 80 个字符以内。

出现 80 线受限于当时的计算机技术,电脑昂贵,显示器小,代码通常要打印出来阅读(打印机仅支持 80 列)。但现在的显示器已经不是问题了,还有很多人会配置带鱼屏来阅读代码,所以这条规则可选择应用。

现在使用 80 线规则有一个好处是可以方便地将两个文件横向并排显示在屏幕上,方便同一个文件前后同时阅读,或者进行文件内容比较。如果你觉得 80 个字符过少,动不动就超过的话,可以考虑 120 线。

另外,如果一行过长,需要考虑是不是变量名过长、嵌套过深,如果是这样,是时候优化代码啦。如果这些都不是,那么就主动换行以便于阅读。

不留未用变量

代码在更新迭代的过程中,难免会遗漏删除不再使用的变量,请留意编译器警告,及时清理。

不留未用变量的目的:一是为了严谨可读;而是为了清除编译器警告。

重视编译警告

并不是所有的编译器警告都是无关紧要的,有些警告关乎程序的命运!

示例代码:

上图例子中的警告就是一个严重的错误,而且是一个典型的很难发现的一个手误。如果你没有看警告,没有察觉到这个问题,那代码跑起来,指不定会出什么幺蛾子。

提示:

警告,还是能修的都给修了吧,没有坏处。

注意数据类型的转换

不匹配的数据类型会导致编译器报警告,警告不能忽视。

预编译条件注意事项

预编译要处理的条件,请使用宏定义,不要在预处理条件里写枚举常量。

因为预编译时,枚举值时恒定的,会导致预编译条件永远成立,参考 CSDN

全局变量的使用

杜绝全局变量满天飞。必须用到全局变量的时候,请务必将其限定在非常小的作用域范围内,能加 static 就加 static,禁止跨文件引用全局变量。

如需要使用另外一个文件里的全局变量,请在那个文件里加 set、get 接口来引用。

空格的使用

typedef struct
{
    void *arg;
} test_struct_t; /* 注意这里 } 后面有一个空格 */

if (1 == 1) /* 注意 if 与 '(' 之间有一个空格,元素 '1' 与操作符 '==' 之间有一个空格 */
{
    /* TODO */
}

花括号 {} 的使用

有的人喜欢在一行代码的末尾使用 {,而有的人喜欢将 {} 单独占一行,我喜欢后者,原因如下:

可以方便地注释 { 前面的一行代码而不影响使用,示例如下:

void main(void)
{
    /* while (1) */
    {

    }
}

所以为了统一,也建议在定义枚举、结构体的时候也将 {} 单独占一行。

注释问题

单行注释请使用这样的形式,避免使用 “//” 形式的注释,因为其实 C++ 类型的注释,某些编译器不支持。

注释只允许出现在语句的前面和后面。

函数的注释,重在描述功能和注意事项。

/* 注释内容与注释标记之间需要留一个空格,像这样 */
/* API 的注释,示例如下 */

/**
* test_func
* 
* @brief Test function. Used to show the name and age.
* 
* @param name Input param, Specify user name.
* @param age  Input param, Specify user age.
* 
* @return void
* 
*/
void test_func(const char *name, uint8_t age);

#if 0#if 1 或成片的代码注释

结论:不建议使用。

如果是测试用途或者其他用途,一定要注释说明这么写的原因,以及是否可以移除或者修改。

如果是遗弃代码,请明显标识遗弃字样,并给出替代接口,或者给出遗弃原因。

bool 类型使用问题

不建议使用 bool 类型。

bool 类型的背景:

C89 没有定义布尔类型,如果你使用 true 和 false,会出现错误。C99 提供了一个头文件 <stdbool.h> 定义了 bool 代表 _Bool,true 代表 1,false 代表 0。只要导入 stdbool.h,就可以使用布尔类型了。

由于这个历史原因,导致一些代码模块会自定义 bool 类型,也自定义类似 TRUE 和 FALSE 这样的宏。

C 标准并没有定义 bool 类型的具体类型,也没有指定 bool 类型的类型大小,不同的 C 标准库对 bool 类型的大小定义不同。

建议使用 <stdint.h> 中的数据类型,同时避免使用 bool 类型要注意 bool 类型的使用注意事项。

bool 类型使用的注意事项:

条件表达式里,bool 类型不能和 true 进行比较。

C99 里定义了 true 和 false,但是只认 false(也就是 0)为唯一有效值,所以 true 是不确定的,不能用在条件表达式里进行比较。

也就是 bool 类型只允许以下比较情况:

if (xxxx)
if (!xxxxx)
if (false == xxxxx)
if (false != xxxxx)

唯独 if (true == xxxxx) 和 if (true!= xxxxx) 不行。

如果需要定义 bool 类型的话,如下定义:

typedef enum
{
    false = 0,
    true  = !false
} bool;

使用标准数据类型

基本数据类型,除非设计不同平台 size 不同的情况,请直接用 C99 <stdint.h> 中定义的标准数据类型(int8_t、uint8_t 等)。而不是使用自行定义的 u8、u16 这些类型。

枚举定义问题

定义一定赋初值。

枚举的大小也不是恒定的,根编译器有关。在配合结构体使用时请注意不要随便使用相对结构体指针的偏移量来读取数据成员的值。

仔细检查 returngoto

主要为了留意是否会有内存泄漏,或者使用了未初始化的变量。

引入的第三方模块带来的格式问题

如果你的项目里引入了第三方模块,但是第三方模块跟你项目的代码风格不一致,这个时候怎么做呢?

为了方便日后更新维护,建议保留其原来的文件格式,不做任何修改(一个好的模块,也基本不需要做修改)。

如果必须要修改第三方模块的代码,视情况转化为统一的风格,轻改动风格不变,重改动统一风格(既然是重改动,肯定需要自己维护这套代码)。

注意在版权范围内引入和修改第三方代码。

中文文档排版规范

参考 中文文案排版指北

参考

仅站在巨人的肩膀上做了些许的总结,向他们致敬!


规范      编程规范

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

 目录

微信公众号:物联网学前班