问题

Linux 内核的 C 代码风格是怎样的?

回答
Linux 内核的 C 代码风格,或者说大家常说的 "Linux Kernel Coding Style",是一套遵循多年的约定俗成,它不仅仅关乎代码的美观,更重要的是为了提升代码的可读性、可维护性和一致性,从而降低开发和调试的难度。这套风格贯穿于内核的每一个角落,是所有内核开发者必须遵守的“潜规则”。

要详细讲讲这套风格,我们可以从几个核心的方面来拆解:

1. 缩进与排版:简洁而有规律

缩进单位: 这是最显眼也是最重要的一个规则。Linux 内核 强制使用 8 个空格作为缩进单位。没错,是空格,不是 Tab 键。很多人初次接触会觉得 8 个空格有点宽,但这是为了保证在各种显示器和终端上的缩进效果尽可能一致,避免因为 Tab 宽度设置不同而造成的显示混乱。

```c
// 正确的缩进
if (condition) {
// 这里是 8 个空格的缩进
do_something();
}

// 错误的缩进
if (condition) {
// 4 个空格
do_something();
}
```

行长度限制: 代码行 不得超过 80 个字符。这个限制是为了让代码在有限的屏幕宽度下更容易阅读,尤其是在使用终端进行开发时。虽然现代显示器越来越宽,但 80 字符的标准已经深入人心。这迫使开发者将过长的语句拆分成多行,从而提高可读性。

```c
// 长的行,需要拆分
some_very_long_function_name(argument1, argument2, argument3,
argument4, argument5);

// 应该拆分成这样:
some_very_long_function_name(argument1, argument2, argument3,
argument4, argument5);
```

括号的使用:
`if`、`for`、`while`、`dowhile` 等控制语句的括号: 即使语句体只有一行,也 必须使用花括号 `{}`。这可以防止未来因为添加了新语句而忘记添加括号导致逻辑错误。
花括号的位置: 左花括号 `{` 必须与控制语句的关键字在同一行,而 右花括号 `}` 必须单独占一行,并且与代码块的起始缩进对齐。

```c
// 正确的括号位置
if (condition) {
do_something();
} else {
do_other_thing();
}

// 错误的括号位置
if (condition)
{
do_something();
}
else {
do_other_thing();
}
```

空格的使用:
运算符两侧: 二元运算符(如 `+`、``、``、`/`、`=`、`==`、`!=`、`&&`、`||` 等)两侧必须有空格。
逗号两侧: 逗号 `,` 后必须有一个空格。
分号两侧: 分号 `;` 前通常不加空格,但分号 `;` 后(尤其是在 `for` 循环的表达式之间)必须有空格。
函数调用和定义: 函数名与开括号 `(` 之间 不加空格。
关键字后: `if`、`for`、`while`、`switch` 等关键字后,与开括号 `(` 之间 必须有空格。

```c
// 正确的空格使用
result = a + b c;
if (x > 0 && y < 10) {
list_add(item, &head);
}

// 错误的空格使用
result=a+bc;
if(x>0 && y<10){
list_add(item, &head);
}
```

2. 命名约定:清晰、一致、表达意图

变量命名: 通常使用 小写字母,单词之间用 下划线 `_` 分隔。名称应该清晰地表达变量的用途。

```c
// 好的变量名
struct task_struct current_task;
unsigned long data_size;

// 不太好的变量名
int i; // 除非是简单的循环计数器
struct ts t;
```

函数命名: 同样是 小写字母,单词之间用 下划线 `_` 分隔。函数名应该描述其执行的操作。

```c
// 好的函数名
schedule_timeout(timeout);
prepare_to_suspend();

// 不太好的函数名
sched();
prep();
```

宏命名: 全大写字母,单词之间用 下划线 `_` 分隔。

```c
define MAX_BUFFER_SIZE 1024
define ERROR_CODE_SUCCESS 0
```

结构体和联合体命名: 通常以 `struct` 或 `union` 开头,后面是 小写字母,单词之间用 下划线 `_` 分隔。

```c
struct file_operations {
// ...
};
```

枚举类型和枚举成员命名: 枚举类型名通常以 `enum` 开头,后面是 小写字母。枚举成员名通常是 全大写字母,并加上一个前缀(例如 `NR_` 表示数量)。

```c
enum {
NR_CPUS = 8,
NR_EVENTS = 16,
};
```

类型定义 (typedef): 对于自定义类型,通常会使用 `typedef`,类型名一般是 小写字母,并以 `_t` 结尾。

```c
typedef unsigned long u64;
typedef int pid_t;
```

3. 注释:解释“为什么”,而非“是什么”

注释风格: Linux 内核 推荐使用 C++ 风格的单行注释 `//`,而不是 C 风格的 `/ ... /`。这是为了避免多行注释中的 `/` 和 `/` 嵌套问题。
注释内容: 注释应该用于解释 代码的意图、复杂逻辑的原理、需要注意的陷阱或潜在的问题,而不是简单地描述代码在做什么。代码本身应该尽量做到自解释。
文档注释: 对于函数、结构体、宏等,如果需要提供更详细的说明(例如用途、参数、返回值等),可以使用特殊的注释格式,例如 `/ ... /`。

```c
// 这是一个简单的例子
// 为什么这里需要这样做?
// 这样做可以避免 XXX 问题
// TODO: 之后需要重构这里

/
@brief 复制用户空间数据到内核空间
@param to: 指向内核空间缓冲区的指针
@param from: 指向用户空间缓冲区的指针
@param n: 要复制的字节数
@return 成功返回复制的字节数,失败返回错误码
/
static inline ssize_t copy_from_user(void to, const void __user from, size_t n)
{
// ... 实现细节 ...
}
```

4. 控制流:清晰、可读、避免过深嵌套

`if`/`else if`/`else` 结构: 尽量保持结构清晰,避免过深的嵌套。如果嵌套过深,可以考虑将部分逻辑提取成独立的函数。
`switch` 语句:
`case` 标签: `case` 标签应与 `switch` 关键字对齐。
`break`: 每个 `case` 块的末尾都必须有 `break` 语句,除非是故意 fallthrough(穿透),此时必须在 `break` 的位置添加 `// fallthrough` 注释进行明确说明。
`default`: 尽量为 `switch` 语句提供 `default` 分支,处理未预期的值。

```c
switch (value) {
case 1:
// ...
break;
case 2:
// ...
// fallthrough
case 3: // fallthrough 也会执行到这里
// ...
break;
default:
// 错误处理
break;
}
```

循环: `for`、`while`、`dowhile` 的使用要根据场景选择最清晰的,并遵循缩进和括号的规则。

5. 函数:短小精悍,专注于单一任务

函数长度: 内核代码鼓励 短小的函数。一个函数最好只做一件事情。过长的函数不仅难以阅读,也更难测试和维护。如果一个函数超过 50100 行(这只是一个粗略的估计),就应该考虑将其拆分。
参数数量: 函数参数不宜过多。如果一个函数需要很多参数,可能说明该函数承担了过多的责任,或者需要重新设计参数结构。
返回值: 函数返回值应该清晰明了。通常返回操作结果或错误码。

6. 错误处理:一致、清晰

错误码: 内核中常用的错误处理机制是返回负的错误码(例如 `EINVAL`、`ENOMEM`)。
早期返回: 好的错误处理风格是 尽早检测错误并返回,避免代码逻辑被错误检查打断。

```c
// 好的错误处理
int do_something(void)
{
int ret;

ret = allocate_resource();
if (ret < 0)
return ret; // 早期返回错误

ret = initialize_resource();
if (ret < 0) {
free_resource(); // 清理已分配的资源
return ret;
}

// ... 正常逻辑 ...

return 0; // 成功
}
```

7. 使用 `__attribute__` 和 `static`:

`static`: 在文件内部使用的函数和变量,如果不是被外部文件引用,应该声明为 `static`,以限制其作用域。
`__attribute__`: GCC 提供了很多 `__attribute__` 来帮助开发者编写更健壮的代码,例如 `__attribute__((warn_unused_result))`、`__attribute__((always_inline))` 等,内核会根据需要使用它们。

8. 内存管理:谨慎、清晰

内存分配: 使用内核提供的内存分配函数,如 `kmalloc()`、`vmalloc()` 等。
内存释放: 必须正确释放已分配的内存。未释放的内存会导致内存泄漏。
指针: 指针操作是 C 语言的难点,在内核开发中尤其需要小心。

9. 结构体成员访问:

点号 `.` 和箭头 `>`: 使用点号访问普通结构体成员,使用箭头访问指向结构体的指针成员。

```c
struct my_struct obj;
struct my_struct ptr = &obj

obj.member = 1;
ptr>member = 2;
```

10. 强制工具: `checkpatch.pl`

Linux 内核提供了一个名为 `checkpatch.pl` 的脚本,用于检查代码是否符合内核的编码风格。开发者在提交代码之前,通常会运行这个脚本来检查潜在的风格问题。这个工具是确保风格一致性的重要保障。

为什么会有这些规则?

可读性: 统一的风格让代码看起来更整洁,开发者可以更快地理解代码的逻辑。
可维护性: 当代码需要修改时,一致的风格可以降低出错的概率。
一致性: 无论谁编写的代码,看起来都像是出自同一人之手,这对于庞大而复杂的项目至关重要。
减少 bug: 某些风格约定(如对所有代码块加花括号)是为了避免常见的编程错误。
便于审查: 代码审查者更容易集中精力在逻辑和功能上,而不是被风格问题分散注意力。

总而言之,Linux 内核的 C 代码风格不是一套僵化的教条,而是一种经过实践检验的、旨在提高代码质量和开发效率的指导原则。遵循这些规则,不仅是对项目负责,也是对其他开发者和未来自己负责。

网友意见

user avatar
是如何用 C 写这么大的项目的?有什么值得借鉴的地方?

类似的话题

  • 回答
    Linux 内核的 C 代码风格,或者说大家常说的 "Linux Kernel Coding Style",是一套遵循多年的约定俗成,它不仅仅关乎代码的美观,更重要的是为了提升代码的可读性、可维护性和一致性,从而降低开发和调试的难度。这套风格贯穿于内核的每一个角落,是所有内核开发者必须遵守的“潜规则.............
  • 回答
    很多使用过 macOS 的朋友,在转向 Linux 时,常常会怀念 macOS 那种优雅、流畅且高度整合的桌面体验。毕竟,macOS 在用户界面和交互设计上一直有其独到之处。那么,Linux 内核的发行版本中,有没有能够提供类似体验的选择呢?答案是肯定的,而且不止一个,只是需要我们花点心思去挑选和配.............
  • 回答
    谈及 Linus Torvalds 和 Linux 内核的技术含量,这绝对是一个可以深入挖掘的话题,而且绝对不是三言两语能说清的。 把它想象成一个规模宏大的、不断进化的城市规划项目,而 Linus 就是那个最初的设计师和现在最核心的建设者。 要评价它的技术含量,我们需要从几个维度来审视。首先,架.............
  • 回答
    Linux 内核自 2.6 版本发布以来,已经过去了相当长的时间(2.6 版本系列从 2004 年开始,一直持续到 2011 年才被 3.0 版本取代),期间经历了无数次迭代和重大的架构性调整。如今的 Linux 内核与 2.6 内核相比,可以说有着天壤之别,在各个方面都发生了翻天覆地的变化。为了详.............
  • 回答
    北京作为中国的科技前沿阵地,Linux内核方面的工作机会可以说相当可观,而且随着开源生态的不断壮大,这类职位的需求也在持续升温。首先,我们要明白,Linux内核本身是整个Linux操作系统的核心,是连接硬件和软件之间的桥梁。它负责管理系统资源,比如CPU、内存、设备驱动等等。因此,从事Linux内核.............
  • 回答
    Intel:为何能成为 Linux 内核的最大贡献者?在 Linux 内核的浩瀚代码海洋中,有一个名字如同一座巍峨的山峰,那就是 Intel。作为全球领先的半导体制造商,Intel 对 Linux 内核的贡献之巨,其影响力贯穿了整个操作系统的核心。这并非偶然,而是其自身战略、技术实力以及对开源社区深.............
  • 回答
    安卓1.0是不是Linux套壳?这个问题,要说清楚,得从根儿上聊聊。简单来说,安卓1.0不是简单的“套壳”,而是 深度集成和定制化开发 的产物,它 构建在 Linux 内核之上,并在此基础上添加了大量的自有组件和框架。我们得一步一步拆解开来看:1. Linux 内核:安卓的基石首先,最关键的一点是,.............
  • 回答
    关于 Linux 内核为何要映射到所有物理内存这个问题,咱们得从几个关键点来掰扯清楚。这可不是什么凭空捏造的规定,而是有着非常扎实的底层逻辑和实际运行需求驱动的。首先,得明白一个最核心的概念:内核就是整个操作系统的“大脑”。它负责管理硬件资源,调度进程,处理各种系统调用,保证程序能够正常运行。如果内.............
  • 回答
    .......
  • 回答
    在 Linux 内核中,为多线程(更准确地说,为进程中的线程)分配和管理栈空间是一个至关重要的环节,它直接关系到程序的执行稳定性、资源利用率以及并发安全性。理解这一模型,需要我们深入到用户空间和内核空间两个层面,以及它们之间的交互。核心概念:栈(Stack)首先,让我们明确栈是什么。栈是一种后进先出.............
  • 回答
    Linux 内核代码,那可真是个庞大且错综复杂的系统,初次接触的人,别说“观看”了,光是搭建好环境,不卡壳地编译一次,就够许多人喝一壶的。真正深入到内核“大佬”们那个级别,他们怎么“看”代码?这可不是简单地打开编辑器, Ctrl+F 一下就完事儿了。这其中蕴含的不仅仅是技术,更是一种方法论,一种对系.............
  • 回答
    华为 Linux 内核贡献者被质疑刷 KPI 的事情,确实在技术圈引起了不少关注和讨论。要理解这件事的真实情况,我们需要从几个层面来看待:事件的起因与核心质疑点:最直接的导火索,大概率是围绕着华为在 Linux 内核社区的贡献数量展开的。有评论者或竞争对手观察到,华为在 Linux 内核社区的提交(.............
  • 回答
    这个问题其实触及了嵌入式Linux系统启动过程中的一些核心概念,涉及到CPU的启动流程、内存映射以及内核映像的加载。我们来详细梳理一下。首先,我们要理解“内存中运行地址0x30008000到内存起始运行地址0x30000000”这个描述。这里的两个地址,0x30008000和0x30000000,显.............
  • 回答
    内核页表与 Linux 伙伴系统之间,用“冲突”来形容可能有些过于绝对,但它们之间确实存在一种微妙的、需要精心管理的协调与权衡。更准确地说,它们是在不同的抽象层次上运作,并且对内存的需求和分配方式有着截然不同的考量,这种差异可能会在特定情况下导致需要仔细处理的复杂性。为了理解这一点,我们需要先分别剖.............
  • 回答
    我们来聊聊Windows和Linux的图形处理,以及X Window协议。Windows和X Window协议:一个不太一样的故事首先明确一点:Windows系统本身并不直接使用X Window协议。X Window系统(通常简称为X Window或X11)是一种网络透明的图形用户界面(GUI)协议.............
  • 回答
    .......
  • 回答
    在未来五年到十年内,Linux 是否会“替代”Windows 成为主流操作系统?这是一个非常有趣且复杂的问题,答案并非简单的“是”或“否”,而是充满了细微之处和多重考量。要回答这个问题,我们需要深入剖析当前的操作系统的格局、Linux 和 Windows 各自的优势劣势,以及影响两者未来走向的关键因.............
  • 回答
    .......
  • 回答
    关于“Linus Torvalds 的短视”这篇文章的真实性,以及其中涉及到的 Linus Torvalds 和 macOS 内核的论述,我们需要进行一个详细的分析。首先,明确一点:关于 Linus Torvalds 对 macOS 内核的评价,并且以此为基础批评他“短视”的文章,其“真实性”取决于.............
  • 回答
    《鸟哥的Linux私房菜》作为一本非常受欢迎的Linux入门书籍,为许多人打开了Linux世界的大门。然而,书中也包含了一些在特定领域可能存在争议或需要更深入理解的内容。我们来逐一评价一下您提到的几点:1. “BSD和GPL很类似”的评价鸟哥在书中可能会提到BSD和GPL的相似之处,这主要体现在它们.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有