文章目录

  • 说明
  • Linux 内核代码风格
    • 1) 缩进
    • 2) 把长的行和字符串打散
    • 3) 大括号和空格的放置
      • 3.1) 空格
    • 4) 命名
    • 5) Typedef
    • 6) 函数
    • 7) 集中的函数退出途径
    • 8) 注释
    • 9) 你已经把事情弄糟了
    • 11) 数据结构
    • 12) 宏,枚举和RTL
    • 13) 打印内核消息
    • 14) 分配内存
    • 15) 内联弊病
    • 16) 函数返回值及命名
    • 17) 不要重新发明内核宏
    • 18) 编辑器模式行和其他需要罗嗦的事情
    • 19) 内联汇编
    • 20) 条件编译
    • 附录 I) 参考
  • git提交规范
    • 代码复查/审查
    • 生成CHANGELOG
    • 规范细则
    • 提交消息格式
    • 修改类型
    • 代码回滚
    • 影响范围
    • 标题
    • 正文
    • 页脚
  • 配置说明
    • vscode配置
    • clang-format配置
    • 代码格式化
      • visual studio code中自动格式化
      • 手动格式化
    • 注释生成
    • git配置(含hook)
    • git 提交规范相关工具
  • 附录
    • 参考
    • 内核.clang-format
    • c++ 应用端.clang-format
    • 注释脚本
    • 部分问题记录
      • 枚举变量换行:行代码不要设置太长,则会自动换行
      • 指针的前的*号 PointerAlignment: Left #Right

说明

公司在组织代码规范化工作,刚好我自己也要弄Qt代码个格式化的事情;刚好两件事一起做了,采用了clang-format。为啥用这个呢,是因为内核在用这个工具,我自己也可以用这个的。我的开发环境是Qt6.1 + Vscode + cmake + clang-format的组合。

Linux 内核代码风格

此处是linux内核翻译版本(V4.15),我是搬运工。

这是一个简短的文档,描述了 linux 内核的首选代码风格。代码风格是因人而异的, 而且我不愿意把自己的观点强加给任何人,但这就像我去做任何事情都必须遵循的原则 那样,我也希望在绝大多数事上保持这种的态度。请 (在写代码时) 至少考虑一下这里 的代码风格。

首先,我建议你打印一份 GNU 代码规范,然后不要读。烧了它,这是一个具有重大象征 性意义的动作。

不管怎样,现在我们开始:

1) 缩进

制表符是 8 个字符,所以缩进也是 8 个字符。有些异端运动试图将缩进变为 4 (甚至 2!) 字符深,这几乎相当于尝试将圆周率的值定义为 3。

理由:缩进的全部意义就在于清楚的定义一个控制块起止于何处。尤其是当你盯着你的 屏幕连续看了 20 小时之后,你将会发现大一点的缩进会使你更容易分辨缩进。

现在,有些人会抱怨 8 个字符的缩进会使代码向右边移动的太远,在 80 个字符的终端 屏幕上就很难读这样的代码。这个问题的答案是,如果你需要 3 级以上的缩进,不管用 何种方式你的代码已经有问题了,应该修正你的程序。

简而言之,8 个字符的缩进可以让代码更容易阅读,还有一个好处是当你的函数嵌套太 深的时候可以给你警告。留心这个警告。

在 switch 语句中消除多级缩进的首选的方式是让 switch 和从属于它的 case 标签对齐于同一列,而不要 两次缩进 case 标签。比如:

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;
}

不要把多个语句放在一行里,除非你有什么东西要隐藏:

if (condition) do_this;do_something_everytime;

也不要在一行里放多个赋值语句。内核代码风格超级简单。就是避免可能导致别人误读 的表达式。

除了注释、文档和 Kconfig 之外,不要使用空格来缩进,前面的例子是例外,是有意为 之。

选用一个好的编辑器,不要在行尾留空格。

2) 把长的行和字符串打散

代码风格的意义就在于使用平常使用的工具来维持代码的可读性和可维护性。

每一行的长度的限制是 80 列,我们强烈建议您遵守这个惯例。

长于 80 列的语句要打散成有意义的片段。除非超过 80 列能显著增加可读性,并且不 会隐藏信息。子片段要明显短于母片段,并明显靠右。这同样适用于有着很长参数列表 的函数头。然而,绝对不要打散对用户可见的字符串,例如 printk 信息,因为这样就 很难对它们 grep。

3) 大括号和空格的放置

C 语言风格中另外一个常见问题是大括号的放置。和缩进大小不同,选择或弃用某种放 置策略并没有多少技术上的原因,不过首选的方式,就像 Kernighan 和 Ritchie 展示 给我们的,是把起始大括号放在行尾,而把结束大括号放在行首,所以:

if (x is true) {we do y
}

这适用于所有的非函数语句块 (if, switch, for, while, do)。比如:

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

不过,有一个例外,那就是函数:函数的起始大括号放置于下一行的开头,所以:

int function(int x)
{body of function
}

全世界的异端可能会抱怨这个不一致性是… 呃… 不一致的,不过所有思维健全的人 都知道 (a) K&R 是 正确的 并且 (b) K&R 是正确的。此外,不管怎样函数都是特 殊的 (C 函数是不能嵌套的)。

注意结束大括号独自占据一行,除非它后面跟着同一个语句的剩余部分,也就是 do 语 句中的 “while” 或者 if 语句中的 “else”,像这样:

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

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

理由:K&R。

也请注意这种大括号的放置方式也能使空 (或者差不多空的) 行的数量最小化,同时不 失可读性。因此,由于你的屏幕上的新行是不可再生资源 (想想 25 行的终端屏幕),你 将会有更多的空行来放置注释。

当只有一个单独的语句的时候,不用加不必要的大括号。

if (condition)action();

if (condition)do_this();
elsedo_that();

这并不适用于只有一个条件分支是单语句的情况;这时所有分支都要使用大括号:

if (condition) {do_this();do_that();
} else {otherwise();
}

3.1) 空格

Linux 内核的空格使用方式 (主要) 取决于它是用于函数还是关键字。(大多数) 关键字 后要加一个空格。值得注意的例外是 sizeof, typeof, alignof 和 __attribute__,这 些关键字某些程度上看起来更像函数 (它们在 Linux 里也常常伴随小括号而使用,尽管 在 C 里这样的小括号不是必需的,就像 struct fileinfo info; 声明过后的 sizeof info)。

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

if, switch, case, for, do, while

但是不要在 sizeof, typeof, alignof 或者 attribute 这些关键字之后放空格。 例如,

s = sizeof(struct file);

不要在小括号里的表达式两侧加空格。这是一个 反例 :

s = sizeof( struct file );

当声明指针类型或者返回指针类型的函数时, * 的首选使用方式是使之靠近变量名 或者函数名,而不是靠近类型名。例子:

char *linux_banner;
unsigned long long memparse(char *ptr, char **retptr);
char *match_strdup(substring_t *s);

在大多数二元和三元操作符两侧使用一个空格,例如下面所有这些操作符:

=  +  -  <  >  *  /  %  |  &  ^  <=  >=  ==  !=  ?  :

但是一元操作符后不要加空格:

&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined

后缀自加和自减一元操作符前不加空格:

++  --

前缀自加和自减一元操作符后不加空格:

++  --

. 和 ->结构体成员操作符前后不加空格。

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

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

4) 命名

C 是一个简朴的语言,你的命名也应该这样。和 Modula-2 和 Pascal 程序员不同, C 程序员不使用类似 ThisVariableIsATemporaryCounter 这样华丽的名字。C 程序员会 称那个变量为 tmp ,这样写起来会更容易,而且至少不会令其难于理解。

不过,虽然混用大小写的名字是不提倡使用的,但是全局变量还是需要一个具描述性的 名字。称一个全局函数为 foo 是一个难以饶恕的错误。

全局变量 (只有当你 真正 需要它们的时候再用它) 需要有一个具描述性的名字,就 像全局函数。如果你有一个可以计算活动用户数量的函数,你应该叫它 count_active_users() 或者类似的名字,你不应该叫它 cntuser() 。

在函数名中包含函数类型 (所谓的匈牙利命名法) 是脑子出了问题——编译器知道那些类 型而且能够检查那些类型,这样做只能把程序员弄糊涂了。难怪微软总是制造出有问题 的程序。

本地变量名应该简短,而且能够表达相关的含义。如果你有一些随机的整数型的循环计 数器,它应该被称为 i 。叫它 loop_counter 并无益处,如果它没有被误解的 可能的话。类似的, tmp 可以用来称呼任意类型的临时变量。

如果你怕混淆了你的本地变量名,你就遇到另一个问题了,叫做函数增长荷尔蒙失衡综 合症。请看第六章 (函数)。

5) Typedef

不要使用类似 vps_t 之类的东西。

对结构体和指针使用 typedef 是一个 错误 。当你在代码里看到:

vps_t a;
这代表什么意思呢?

相反,如果是这样

struct virtual_container *a;

你就知道 a 是什么了。

很多人认为 typedef 能提高可读性 。实际不是这样的。它们只在下列情况下有用:

  • a. 完全不透明的对象 (这种情况下要主动使用 typedef 来 隐藏 这个对象实际上 是什么)。

    例如: pte_t 等不透明对象,你只能用合适的访问函数来访问它们。

    Note:
    不透明性和 “访问函数” 本身是不好的。我们使用 pte_t 等类型的原因在于真 的是完全没有任何共用的可访问信息。

  • b.清楚的整数类型,如此,这层抽象就可以 帮助 消除到底是 int 还是 long 的混淆。

    u8/u16/u32 是完全没有问题的 typedef,不过它们更符合类别 (d) 而不是这里。

    Note

    要这样做,必须事出有因。如果某个变量是 unsigned long ,那么没有必要

typedef unsigned long myflags_t;
 不过如果有一个明确的原因,比如它在某种情况下可能会是一个 unsigned int 而在其他情况下可能为 unsigned long ,那么就不要犹豫,请务必使用 typedef。
  • c. 当你使用 sparse 按字面的创建一个 新 类型来做类型检查的时候。

  • d.和标准 C99 类型相同的类型,在某些例外的情况下。

    虽然让眼睛和脑筋来适应新的标准类型比如 uint32_t 不需要花很多时间,可 是有些人仍然拒绝使用它们。

    因此,Linux 特有的等同于标准类型的 u8/u16/u32/u64 类型和它们的有符号 类型是被允许的——尽管在你自己的新代码中,它们不是强制要求要使用的。

    当编辑已经使用了某个类型集的已有代码时,你应该遵循那些代码中已经做出的选 择。

  • e.可以在用户空间安全使用的类型。

    在某些用户空间可见的结构体里,我们不能要求 C99 类型而且不能用上面提到的 u32 类型。因此,我们在与用户空间共享的所有结构体中使用 __u32 和类似 的类型。

可能还有其他的情况,不过基本的规则是 永远不要 使用 typedef,除非你可以明 确的应用上述某个规则中的一个。

总的来说,如果一个指针或者一个结构体里的元素可以合理的被直接访问到,那么它们 就不应该是一个 typedef。

6) 函数

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

一个函数的最大长度是和该函数的复杂度和缩进级数成反比的。所以,如果你有一个理 论上很简单的只有一个很长 (但是简单) 的 case 语句的函数,而且你需要在每个 case 里做很多很小的事情,这样的函数尽管很长,但也是可以的。

不过,如果你有一个复杂的函数,而且你怀疑一个天分不是很高的高中一年级学生可能 甚至搞不清楚这个函数的目的,你应该严格遵守前面提到的长度限制。使用辅助函数, 并为之取个具描述性的名字 (如果你觉得它们的性能很重要的话,可以让编译器内联它 们,这样的效果往往会比你写一个复杂函数的效果要好。)

函数的另外一个衡量标准是本地变量的数量。此数量不应超过 5-10 个,否则你的函数 就有问题了。重新考虑一下你的函数,把它分拆成更小的函数。人的大脑一般可以轻松 的同时跟踪 7 个不同的事物,如果再增多的话,就会糊涂了。即便你聪颖过人,你也可 能会记不清你 2 个星期前做过的事情。

在源文件里,使用空行隔开不同的函数。如果该函数需要被导出,它的 EXPORT 宏 应该紧贴在它的结束大括号之下。比如:

int system_is_up(void)
{return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);

在函数原型中,包含函数名和它们的数据类型。虽然 C 语言里没有这样的要求,在 Linux 里这是提倡的做法,因为这样可以很简单的给读者提供更多的有价值的信息。

7) 集中的函数退出途径

虽然被某些人声称已经过时,但是 goto 语句的等价物还是经常被编译器所使用,具体 形式是无条件跳转指令。

当一个函数从多个位置退出,并且需要做一些类似清理的常见操作时,goto 语句就很方 便了。如果并不需要清理操作,那么直接 return 即可。

选择一个能够说明 goto 行为或它为何存在的标签名。如果 goto 要释放 buffer, 一个不错的名字可以是 out_free_buffer: 。别去使用像 err1: 和 err2: 这样的GW_BASIC 名称,因为一旦你添加或删除了 (函数的) 退出路径,你就必须对它们 重新编号,这样会难以去检验正确性。

使用 goto 的理由是: