本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
C 语言代码风格之 Linux 内核代码风格
我是陈晓 · 285浏览 · 发布于2021-03-26 +关注

本文简短描述了 Linux 内核推荐的代码风格。代码风格是非常个性化的,但这是 Linux 内核必须维持的准则,对于很多其他领域的代码,该规范也具有参考意义。

首先,请打印出 GNU 代码规范,不要阅读,烧掉它们,这是一个很棒的象征性手势。

1 缩进

Tab 占 8 个字符,因此缩进也是 8 个字符。有一些异端运动试图减少缩进至 4 个字符(甚至 2 个字符),类似的尝试是将 PI 的值定义为 3。

原因:缩进的整体思想是明确定义控制块的开始和结束位置。特别是当你连续看屏幕 20 个小时时,你会发现当缩进值较大时,注意到缩进会更加轻松。

现在,有些人表示 8 个字符的缩进使代码向右移得太远,难以在宽度为 80 字符的终端屏幕上阅读。

这个问题的答案是:如果你需要三个以上的缩进级别,无论如何程序都会被搞砸,应当修改程序。

简而言之,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
}

全世界的异端人士都声称这种矛盾是矛盾的,但是所有有正确思想的人们都知道 K&R 是正确的。

此外,函数还是很特殊的(你不能将它们嵌套在 C 代码里)。

请注意,闭括号所在的行是空的,除非在其后接同一语句的延续,即 while 在 do 语句后或 else 在 if 语句后,像这样:

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();else
        do_that();

如果多个条件语句中只有一个分支是单个语句,则不适用该规则。在这种情况下,需要在所有的分支中都使用大括号:

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

3.1 空格#

Linux 内核风格使用空格的样式(主要)取决于函数与关键字的用法。

在大多数关键字之后使用一个空格。值得注意的例外是 sizeof, typeof, alignof, __attribute__,看起来有点像函数(通常在 Linux 中使用时需要加括号,虽然它们并没有要求,如在 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,这样更容易编写,但同时也更难理解。

虽然不赞成使用大小写混合的命名方式,但描述性名称命名全局函数是必须的。

全局变量(仅在确实需要它们时才使用)与全局函数一样,都需要使用具有描述性的名称命名。如果现在有一个函数用来统计活跃用户数,应该将它命名为 count_active_users() 或类似的,而不应该命名为 cntusr()

将函数的类型编码为名称(所谓的匈牙利命名法)是不好的——编译器无论如何都知道它们的类型并且可以检查它们,这样只会使程序员感到困惑。难怪 MicroSoft 写出了很多古怪的程序。

局部变量名和应该简单明了,切中要害。如果你有一些随机的整数循环变量,可以将它命名为 i。如果该程序没有可能被误解,命名为 loop_counter 是没有必要的。同样,tmp 几乎可以用于任何类型的保存临时值的变量。

如果你害怕混淆你的局部变量名,你会遇到另一个问题,它通常被称为“生长激素功能失调综合征”。参见第六章(函数)。

5 Typedefs

请不要使用诸如 vps_t 之类的类型别名。对结构体和指针使用此类别名是一种错误。当你在源码中看到

vps_t a;

这是什么意思?

相反,如果是这样

struct virtual_container *a;

你就能知道 a 实际上到底是什么。

许多人认为 typedefs 提高了可读性。但并不是这样,它们仅对以下内容有用:

  1. 完全不透明的对象(typedef 用于隐藏对象的内容)。

    示例:pte_t 等不透明的对象,你只能通过合适的访问函数进行访问。

    注意:不透明性和访问函数并不是一种好的方法。之所以将它们用于类似 pte_t 的对象,是因为它们确实存在不可访问的信息。

  2. 明确整数类型,这种抽象有助于避免混淆 int 和 longu8/u16/u32 是完美的 typedefs

    注意:

    如果如果有任何量是 unsigned long,那就没有理由使用 typedef unsigned long myflags_t

    但是如果有明确的理由说明为什么在某些情况下它可能是 unsigned long,而在另一些配置下可能是 unsigned long,那就继续使用 typedef

  3. 当你使用 sparse 创建一个用于类型检查的新类型。

  4. 在某些特殊情况下,与 C99 标准类型相同的新类型。

    尽管只需要很短的时间就可以使眼睛和大脑适应像这样的标准类型 uint32_t,但是无论如何,有些人还是反对使用它们。

    因此,Linux 特定类型 u8/u16/u32/u64 及其有符号类型被允许定义别名,尽管它们在新代码中不是必须的。

    当编辑已使用一种或另一组类型的现有代码时,应当遵循该代码中的现有选择。

  5. 在用户空间保持类型安全。

    在用户空间可见的某些结构体中,我们不能使用 C99 标准类型,也不能使用类似 u32 的形式。因此,我们应当在所有与用户空间共享的结构的体中使用 __u32 和类似类型。

也许还有其他情况,但是一个基本规则是:除非你可以清楚地匹配上述规则之一,否则不要使用 typedef

通常,指针或者具有可以合理地直接访问元素的结构体永远都不应该使用 typedef

6 函数

函数应该简洁而优美,每个函数只做一件事。它们应该适合一到两个文本(