C语言规范整理

C语言规范整理

头文件

  • 每个.c文件应该有一个关联的.h文件。

    如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。

  • 所有头文件应该有#define防护装置,以防止多个包含。

    1
    2
    3
    4
    5
    6
    #ifndef FOO_BAR_BAZ_H
    #define FOO_BAR_BAZ_H

    ...

    #endif // FOO_BAR_BAZ_H

    使用FILENAME_H代替_FILENAME_H_,是因为一般以”_“和”__“开头的标识符为系统保留或者标准库使用,在有些静态检查工具中,若全局可见的标识符以”_”开头会给出告警

  • 所有项目的头文件都应列为项目源目录的后代,而不使用UNIX目录快捷方式 .(当前目录)或.. (父目录)。例如, google-awesome-project/src/base/logging.h 应包括为:

    1
    #include "base/logging.h"
  • 包含的名称和顺序:Related header, C library, C++ library, other libraries’ .h, your project’s .h。避免隐藏的依赖关系。

    In dir/foo.cc or dir/foo_test.cc, whose main purpose is to implement or test the stuff in dir2/foo2.h, order your includes as follows:

    1. dir2/foo2.h.
    2. C system files.
    3. C++ system files.
    4. Other libraries’ .h files.
    5. Your project’s .h files.
  • 禁止头文件循环依赖。

    头文件循环依赖,指a.h包含b.h, b.h包含c.h, c.h包含a.h之类导致任何一个头文件修改,都
    导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h, b.h包含
    c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

  • 不要在头文件中定义变量

    在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

  • 不要在extern “C”中包含头文件

    在extern “C”中包含头文件, 会导致extern “C”嵌套, Visual Studio对extern “C”嵌套层次有
    限制,嵌套层次太多会编译错误。

缩进

  • 制表符是 8 个字符,所以缩进也是 8 个字符。

    1
    2
    3
    4
    int main()
    {
    return 0;
    }

    缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的屏幕连续看了20 小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。简而言之,8 个字符的缩进可以让代码更容易阅读

  • 使用空格缩进。不要在代码中使用制表符。当您按Tab键时,您应该将编辑器设置为发出空格。

  • 不要出现超过3级以上的缩进

    如果你需要 3 级以上的缩进,不管用何种方式你的代码已经有问题了,应该修正你的程序。

  • switch语句和从属于它的case标签对齐于同一列,不要两次缩进case

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    switch (suffix) {
    case 'G':
    case 'g':
    mem <<= 30;
    break;
    case 'M':
    case 'm':
    mem <<= 20;
    break;
    case 'K':
    case 'k':
    mem <<= 10;
    /* fall through */
    default:
    break;
    }

括号和空格

  • (大多数) 关键字后要加一个空格。值得注意的例外是 sizeof, typeof, alignof 和 __attribute__,这些关键字某些程度上看起来更像函数 (它们在 Linux 里也常常伴随小括号而使用

    在这些关键字之后放一个空格:

    1
    if, switch, case, for, do, while
  • 在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:

    1
    =  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :
  • 一元操作符后不要加空格:

    1
    &  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined
  • 后缀自加和自减一元操作符前不加空格

    1
    ++  --
  • 前缀自加和自减一元操作符后不加空格

    1
    ++  --
  • .-> 结构体成员操作符前后不加空格

  • 行尾不留空白

    有些可以自动缩进的编辑器会在新行的行首加入适量的空白,然后 你就可以直接在那一行输入代码。不过假如你最后没有在那一行输入代码,有些编辑器 就不会移除已经加入的空白,就像你故意留下一个只有空白的行。包含行尾空白的行就 这样产生了。

    当 git 发现补丁包含了行尾空白的时候会警告你,并且可以应你的要求去掉行尾空白; 不过如果你是正在打一系列补丁,这样做会导致后面的补丁失败,因为你改变了补丁的 上下文。

  • 小括号里的表达式两侧不加空格

    正例

    1
    s = sizeof(struct file);

    反例

    1
    s = sizeof( struct file );
  • 逗号与分号后要添加一个空格 ,逗号与分号前不加空格

函数

  • 函数应该简短而漂亮,并且只完成一件事情。

    函数应该可以一屏或者两屏显示完 (我们都知道ISO/ANSI 屏幕大小是 80x24),只做一件事情,而且把它做好。

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

    重复代码提炼成函数可以带来维护成本的降低

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

    函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工
    作。函数的参数过多同时也会增大测试的工作量。
    函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。

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

    如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。使用static确保只是
    在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。

命名

  • unix like风格:单词用小写字母,每个单词直接用下划线“_”分割,例如text_mutex, kernel_text_address。

  • 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,
    避免使人产生误解。

    尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。

    好的命名:

    1
    2
    int error_number;
    int number_of_completed_connection;

    不好的命名:

    1
    2
    3
    int n;
    int nerr;
    int n_comp_conns;
  • 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音

    较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些
    单词有大家公认的缩写,常用单词的缩写必须统一。

    示例:一些常见可以缩写的例子:

    argument 可缩写为 arg
    buffer 可缩写为 buff
    clock 可缩写为 clk
    command 可缩写为 cmd
    compare 可缩写为 cmp
    configuration 可缩写为 cfg
    device 可缩写为 dev
    error 可缩写为 err
    hexadecimal 可缩写为 hex
    increment 可缩写为 inc、
    initialize 可缩写为 init
    maximum 可缩写为 max
    message 可缩写为 msg
    minimum 可缩写为 min
    parameter 可缩写为 para
    previous 可缩写为 prev
    register 可缩写为 reg
    semaphore 可缩写为 sem
    statistic 可缩写为 stat
    synchronize 可缩写为 sync
    temp 可缩写为 tmp

  • 文件命名统一采用小写字符。

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

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

    全局变量十分危险,通过前缀使得全局变量更加醒目, 促使开发人员对这些变量的使用更加小
    心。其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。

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

  • 使用名词或者形容词+名词方式命名变量。

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

    找到当前进程的当前目录
    DWORD GetCurrentDirectory( DWORD BufferLength, LPTSTR Buffer );

  • 宏的命名:对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线”_“的方式命名(枚举同样建议使用此方式定义)

    1
    #define PI_ROUNDED 3.14
  • 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线”_“开头和结尾。

    一般来说, “_“开头、结尾的宏都是一些内部的定义

注释

  • 优秀的代码可以自我解释,不通过注释即可轻易读懂

    优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码
    往往存在坏味道,需要重构

    示例:注释不能消除代码的坏味道:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /* 判断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;
    }

    注:注释最好为全英文注释

    重构后,不需要注释 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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;
    }
  • 注释的内容要清楚、明了,含义准确,防止注释二义性。

    歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。

  • 注释应放在其代码上方相邻位置或右方,不可放在下面。 如放于上方则需与其上面的代码用
    空行隔开,且与下方代码缩进相同

    1
    2
    3
    4
    /* active statistic task number */
    #define MAX_ACT_TASK_NUMBER 1000

    #define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */
  • 使用///* */ 语法,只要你是一致的

  • 几乎每个函数声明应该在它之前有注释,它描述函数的功能和使用方法。这些注释只有在功能简单明了的情况下才可以省略

    函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的
    要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、 设计约束等

  • 头文件头部添加注释来说明头文件的文件名,版权,作者,用途摘要

排版和格式

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

    1
    2
    3
    4
    5
    6
    7
    8
    if (!valid_ni(ni))
    {
    // program code
    ...
    }

    repssn_ind = ssn_data[index].repssn_index;
    repssn_ni = ssn_data[index].ni;
  • 每行代码长度应不超过 80 个字符

    换行时有如下建议:

    • 换行时要增加一级缩进,使代码可读性更好;
    • 低优先级操作符处划分新行; 换行时操作符应该也放下来,放在新行首;
    • 换行时建议一个完整的语句放在一行,不要根据字符数断行
  • 非ASCII字符应该是罕见的,必须使用UTF-8格式化。

  • 不要把多个语句放在一行里,即一行只写一条语句

    示例:

    1
    int a = 5; int b= 10; //不好的排版

    较好的排版

    1
    2
    int a = 5;
    int b= 10;
  • if、 for、 do、 while、 case、 switch、 default等语句独占一行。