Linux内核编码风格

快速链接:
.
👉👉👉 个人博客笔记导读目录(全部) 👈👈👈


说明:
在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 5.14

Linux内核编码风格
这是一个简短的文档,描述了 linux 内核的首选编码风格。

1、 缩进
  • 制表符是 8 个字符,因此缩进也是 8 个字符。单行长度的首选限制是 80个字符,超过 80 字符的语句应该被分成合理的块

  • switch及其下属case的标签在同一列中
    在这里插入图片描述

  • 不要在一行中放置多个作业
    在这里插入图片描述

  • 在注释、文档和 Kconfig 之外,使用TAB作为缩进

  • 远不要破坏用户可见的字符串,例如 printk 消息

3、放置大括号和空格

大括号和空格的使用,这适用于所有非函数语句块(if、switch、for、while、do).
在这里插入图片描述

  • sizeof、typeof、alignof 或 attribute 后的括号,不需要放空格
    在这里插入图片描述

  • 在大多数二元和三元运算符周围(两侧)使用一个空格(= + - < > * / % | & ^ <= >= == != ? :)

  • 一元运算符后没有空格(& * + - ~ ! sizeof typeof alignof attribute defined)

4、 命名
  • C 是一种 Spartan 语言,您的命名约定应遵循此规则。不赞成大小写混合的名称.
  • 全局变量需要具有描述性名称,如您有一个计算活跃用户数的函数,您应该调用它count_active_users()或类似的函数, 而不应该调用它cntusr()。
  • 局部 变量名应该简短,切中要害。如果你有一些随机整数循环计数器,它可能应该被调用i。
  • 对于符号名称和文档,避免引入“主/从”(或独立于“主”的“从”)和“黑名单/白名单”的新用法,
    “主/从”的推荐替代品是:
    ‘{primary,main} / {secondary,replica,subordinate}’ ‘{initiator,requester} / {target,responder}’ ‘{controller,host} / {device,worker,proxy}’ ‘leader / follower’ 'director / performer
    “黑名单/白名单”的推荐替代品是:
    ‘denylist / allowlist’ ‘blocklist / passlist’
5、 类型定义

请不要使用诸如vps_t. 将 typedef 用于结构和指针是错误的。当你看到一个

vps_t a;
在源代码中,它是什么意思?相反,如果它说

struct virtual_container *a;
你实际上可以说出是什么a。

很多人认为 typedefs . 不是这样。它们仅用于:help readability

完全不透明的对象(typedef 被主动用于隐藏 对象是什么)。

示例:pte_t等,您只能使用适当的访问器函数访问的不透明对象。

笔记

不透明,本身就不好。我们将它们用于 pte_t 等内容的原因是那里确实有绝对零可移植的可访问信息。accessor functions

清除整数类型,其中抽象有助于避免混淆,无论是int还是long。

u8/u16/u32 是非常好的 typedef,尽管它们比这里更适合 (d) 类。

笔记

再次 - 这需要有一个原因。如果有 ,那就没有理由去做unsigned long

typedef unsigned long myflags_t;

但是如果有一个明确的理由说明为什么它在某些情况下可能是 an而在其他配置下可能是 ,那么请务必继续使用 typedef。unsigned intunsigned long

当您使用 sparse 从字面上创建用于类型检查的新类型时。

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

虽然眼睛和大脑只需要很短的时间就可以适应像 那样的标准类型uint32_t,但还是有人反对使用它们。

因此,u8/u16/u32/u64允许使用特定于 Linux 的类型及其与标准类型相同的带符号的等效项——尽管它们在您自己的新代码中不是强制性的。

在编辑已经使用一个或另一组类型的现有代码时,您应该符合该代码中的现有选择。

在用户空间中安全使用的类型。

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

也许还有其他情况,但规则基本上应该是永远不要使用 typedef,除非您可以清楚地匹配这些规则之一。

通常,指针或具有可以合理直接访问的元素的结构永远不应该是 typedef。

6、 函数

函数应该短小精悍,只做一件事。它们应该适合一两屏文本(ISO/ANSI 屏幕尺寸是 80x24,众所周知),并且做一件事并且做得好。

函数的最大长度与该函数的复杂性和缩进级别成反比。所以,如果你有一个概念上简单的函数,它只是一个长(但简单)的 case 语句,你必须为很多不同的情况做很多小事情,那么有一个更长的函数是可以的。

但是,如果您有一个复杂的函数,并且您怀疑一个没有天赋的高中一年级学生甚至可能不了解该函数的全部内容,那么您应该更加严格地遵守最大限制。使用具有描述性名称的辅助函数(如果您认为它对性能至关重要,您可以要求编译器将它们内联,并且它可能会比您做得更好)。

函数的另一个度量是局部变量的数量。他们不应该超过 5-10,否则你做错了什么。重新思考这个功能,把它分成更小的部分。人脑通常可以轻松地跟踪大约 7 种不同的事物,如果再多一些,就会感到困惑。您知道自己很出色,但也许您想了解 2 周后您做了什么。

在源文件中,用一个空行分隔函数。如果函数被导出,它的EXPORT宏应该紧跟在函数结束括号之后。例如:

int system_is_up(void)
{
return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);
6.1) 函数原型
在函数原型中,包括参数名称及其数据类型。虽然这不是 C 语言所要求的,但它在 Linux 中是首选,因为它是一种为读者添加有价值信息的简单方法。

不要在extern函数声明中使用关键字,因为这会使行变长并且不是绝对必要的。

在编写函数原型时,请保持元素的顺序规则。例如,使用这个函数声明示例:

__init void * __must_check action(enum magic value, size_t size, u8 count,
char *fmt, …) __printf(4, 5) __malloc;
函数原型的首选元素顺序是:

存储类(下面,,请注意, 从技术上讲,这是一个属性,但被视为)static __always_inline__always_inlineinline

存储类属性(这里,__init-即节声明,还有类似的东西__cold)

返回类型(这里,)void *

返回类型属性(此处为__must_check)

函数名(这里,action)

函数参数(此处为 ,注意应始终包含参数名称)(enum magic value, size_t size, u8 count, char *fmt, …)

函数参数属性(这里,)__printf(4, 5)

函数行为属性(此处,__malloc)

注意对于一个函数定义(即实际的函数体),编译器不允许在函数参数之后有函数参数属性。在这些情况下,它们应该在存储类属性之后(例如 ,与上面的声明示例相比,请注意下面的更改位置):__printf(4, 5)

static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value,
size_t size, u8 count, char *fmt, …) __malloc
{

}

7、 功能集中退出

尽管有些人不赞成,但编译器经常以无条件跳转指令的形式使用 goto 语句的等效项。

当一个函数从多个位置退出并且必须完成一些诸如清理之类的常见工作时,goto 语句就派上用场了。如果不需要清理,则直接返回。

选择标签名称,说明 goto 的作用或 goto 存在的原因。一个好名字的例子可能是out_free_buffer:如果 goto 释放buffer. 避免使用 GW-BASIC 名称err1:和err2:,因为如果您添加或删除出口路径,您将不得不重新编号它们,并且它们无论如何都难以验证正确性。

使用 goto 的基本原理是:

无条件语句更容易理解和遵循

嵌套减少

防止在进行修改时不更新各个出口点而导致的错误

节省编译器的工作以优化冗余代码;)

int fun(int a)
{
int result = 0;
char *buffer;

    buffer = kmalloc(SIZE, GFP_KERNEL);
    if (!buffer)
            return -ENOMEM;

    if (condition1) {
            while (loop1) {
                    ...
            }
            result = 1;
            goto out_free_buffer;
    }
    ...

out_free_buffer:
kfree(buffer);
return result;
}
需要注意的一种常见错误类型如下所示:one err bugs

err:
kfree(foo->bar);
kfree(foo);
return ret;
此代码中的错误是在某些退出路径上foo为 NULL。通常对此的解决方法是将其分成两个错误标签err_free_bar:和 err_free_foo::

err_free_bar:
kfree(foo->bar);
err_free_foo:
kfree(foo);
return ret;
理想情况下,您应该模拟错误以测试所有退出路径。

8、 评论

评论是好的,但也有过度评论的危险。永远不要试图在注释中解释你的代码是如何工作的:编写代码以便工作是显而易见的要好得多,而解释写得不好的代码是浪费时间。

通常,您希望您的注释说明您的代码做了什么,而不是如何做。另外,尽量避免在函数体中放置注释:如果函数太复杂以至于你需要单独注释它的一部分,你可能应该回到第 6 章一段时间。你可以做一些小的评论来注意或警告一些特别聪明(或丑陋)的东西,但尽量避免过度。相反,将注释放在函数的开头,告诉人们它做了什么,以及它为什么这样做。

注释内核 API 函数时,请使用 kernel-doc 格式。看到这些文件在文件/文件引导/并 scripts/kernel-doc了解详情。

长(多行)注释的首选样式是:

/*

  • This is the preferred style for multi-line
  • comments in the Linux kernel source code.
  • Please use it consistently.
  • Description: A column of asterisks on the left side,
  • with beginning and ending almost-blank lines.
    */
    对于 net/ 和 drivers/net/ 中的文件,长(多行)注释的首选样式略有不同。

/* The preferred comment style for files in net/ and drivers/net

  • looks like this.
  • It is nearly the same as the generally preferred comment style,
  • but there is no initial almost-blank line.
    */
    注释数据也很重要,无论它们是基本类型还是派生类型。为此,每行只使用一个数据声明(多个数据声明没有逗号)。这样您就可以对每个项目进行简短的评论,解释其用途。
9、你把它弄得一团糟

没关系,我们都这样。您的长期 Unix 用户助手可能已经告诉您,它会自动为您格式化 C 源代码,并且您已经注意到是的,它确实这样做了,但是它使用的默认值并不理想(事实上,它们比随机输入更糟糕——无数的猴子输入 GNU emacs 永远不会成为一个好的程序)。GNU emacs

因此,您可以摆脱 GNU emacs,或将其更改为使用更合理的值。要执行后者,您可以在 .emacs 文件中粘贴以下内容:

(defun c-lineup-arglist-tabs-only (ignored)
“Line up argument lists by tabs, not spaces”
(let* ((anchor (c-langelem-pos c-syntactic-element))
(column (c-langelem-2nd-pos c-syntactic-element))
(offset (- (1+ column) anchor))
(steps (floor offset c-basic-offset)))
(* (max steps 1)
c-basic-offset)))

(dir-locals-set-class-variables
'linux-kernel
'((c-mode . (
(c-basic-offset . 8)
(c-label-minimum-indentation . 0)
(c-offsets-alist . (
(arglist-close . c-lineup-arglist-tabs-only)
(arglist-cont-nonempty .
(c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only))
(arglist-intro . +)
(brace-list-intro . +)
(c . c-lineup-C-comments)
(case-label . 0)
(comment-intro . c-lineup-comment)
(cpp-define-intro . +)
(cpp-macro . -1000)
(cpp-macro-cont . +)
(defun-block-intro . +)
(else-clause . 0)
(func-decl-cont . +)
(inclass . +)
(inher-cont . c-lineup-multi-inher)
(knr-argdecl-intro . 0)
(label . -1000)
(statement . 0)
(statement-block-intro . +)
(statement-case-intro . +)
(statement-cont . +)
(substatement . +)
))
(indent-tabs-mode . t)
(show-trailing-whitespace . t)
))))

(dir-locals-set-directory-class
(expand-file-name “~/src/linux-trees”)
'linux-kernel)
这将使 emacs 更好地使用下面 C 文件的内核编码风格~/src/linux-trees。

但是,即使您未能让 emacs 进行合理的格式化,也并非一切都会丢失:使用indent.

现在,再次重申,GNU 缩进具有与 GNU emacs 相同的脑死设置,这就是为什么您需要为其提供一些命令行选项的原因。然而,这还不算太糟糕,因为即使是 GNU indent 的制造者也承认 K&R 的权威(GNU 人并不邪恶,他们只是在这件事上被严重误导了),所以你只需给 indent 选项(代表),或使用 ,以最新样式缩进。-kr -i8K&R, 8 character indentsscripts/Lindent

indent有很多选项,尤其是在评论重新格式化时,您可能需要查看手册页。但请记住:indent这不是对糟糕编程的修复。

请注意,您还可以使用该clang-format工具来帮助您处理这些规则,自动快速重新格式化部分代码,并查看完整文件以发现编码风格错误、拼写错误和可能的改进。它对于排序#includes、对齐变量/宏、重排文本和其他类似任务也很方便。查看文件clang-format 了解更多详情。

10、Kconfig配置文件

对于整个源代码树中的所有 Kconfig* 配置文件,缩进都有些不同。config定义下的行缩进一个制表符,而帮助文本缩进两个空格。例子:

config AUDIT
bool “Auditing support”
depends on NET
help
Enable auditing infrastructure that can be used with another
kernel subsystem, such as SELinux (which requires this for
logging of avc messages output). Does not do system-call
auditing without CONFIG_AUDITSYSCALL.
严重危险的功能(例如对某些文件系统的写支持)应该在其提示字符串中突出显示这一点:

config ADFS_FS_RW
bool “ADFS write support (DANGEROUS)”
depends on ADFS_FS

有关配置文件的完整文档,请参阅文件 Kconfig Language。

11、数据结构

在创建和销毁它们的单线程环境之外具有可见性的数据结构应该始终具有引用计数。在内核中,垃圾收集不存在(并且在内核之外垃圾收集缓慢且低效),这意味着您绝对必须对所有使用进行引用计数。

引用计数意味着您可以避免锁定,并允许多个用户并行访问数据结构——而不必担心结构突然从他们下面消失,因为他们睡了一段时间或做了其他事情。

注意,锁不是引用计数的替代品。锁定用于保持数据结构的一致性,而引用计数是一种内存管理技术。通常两者都需要,并且不要将它们相互混淆。

许多数据结构确实可以有两个级别的引用计数,当存在不同的classes. 子类计数计算子类用户的数量,当子类计数变为零时,只减少一次全局计数。

此类示例multi-level-reference-counting可以在内存管理(:mm_users 和 mm_count)和文件系统代码(:s_count 和 s_active)中找到。struct mm_structstruct super_block

请记住:如果另一个线程可以找到您的数据结构,而您没有引用计数,那么您几乎肯定有错误。

12)、宏、枚举和 RTL

在枚举中定义常量和标签的宏的名称大写。

#define CONSTANT 0x12345
定义多个相关常量时,首选枚举。

大写的宏名称值得赞赏,但类似函数的宏可能以小写命名。

通常,内联函数比类似函数的宏更可取。

包含多个语句的宏应该包含在 do - while 块中:

#define macrofun(a, b, c)
do {
if (a == 5)
do_this(b, c);
} while (0)
使用宏时要避免的事情:

影响控制流的宏:

#define FOO(x)
do {
if (blah(x) < 0)
return -EBUGGERED;
} while (0)
是一个非常糟糕的主意。它看起来像一个函数调用但退出了calling 函数;不要破坏那些将阅读代码的人的内部解析器。

依赖于具有魔法名称的局部变量的宏:

#define FOO(val) bar(index, val)
可能看起来是件好事,但是当人们阅读代码时它会令人困惑,而且它很容易因看似无害的更改而损坏。

  1. 带有用作左值的参数的宏:FOO(x) = y;如果有人将 FOO 转换为内联函数,则会咬你。

  2. 忘记优先级:使用表达式定义常量的宏必须将表达式括在括号中。请注意使用参数的宏的类似问题。

#define CONSTANT 0x4000
#define CONSTEXP (CONSTANT | 3)
5) 在类似函数的宏中定义局部变量时的命名空间冲突:

#define FOO(x)
({
typeof(x) ret;
ret = calc_ret(x);
(ret);
})
ret 是局部变量的通用名称 - __foo_ret 不太可能与现有变量发生冲突。

cpp 手册详尽地处理了宏。gcc 内部手册还涵盖了内核中经常与汇编语言一起使用的 RTL。

13、 打印内核消息

内核开发人员喜欢被视为有文化的人。请注意内核消息的拼写以给人留下好印象。不要使用不正确的收缩,如dont; 使用或代替。使信息简洁、清晰、不含糊。do notdon’t

内核消息不必以句点终止。

在括号 (%d) 中打印数字不会增加任何值,应避免使用。

<linux/dev_printk.h> 中有许多驱动程序模型诊断宏,您应该使用它们来确保消息与正确的设备和驱动程序匹配,并使用正确的级别进行标记:dev_err()、dev_warn()、 dev_info() 等等。对于没有与特定设备,在<linux / printk.h>定义相关联的消息pr_notice(),pr_info(), pr_warn(),pr_err(),等。

想出好的调试消息可能是一个很大的挑战。一旦拥有它们,它们就可以为远程故障排除提供巨大帮助。然而,调试消息打印的处理方式与打印其他非调试消息不同。虽然其他 pr_XXX() 函数无条件打印, pr_debug()但不会;它默认被编译出来,除非定义了 DEBUG 或设置了 CONFIG_DYNAMIC_DEBUG。对于 dev_dbg() 也是如此,相关约定使用 VERBOSE_DEBUG 将 dev_vdbg() 消息添加到已由 DEBUG 启用的消息中。

许多子系统都有 Kconfig 调试选项,可以在对应的 Makefile 中开启 -DDEBUG;在其他情况下,特定文件#define DEBUG。当应该无条件打印调试消息时,例如如果它已经在调试相关的#ifdef 部分中,则可以使用 printk(KERN_DEBUG …)。

14、分配内存

内核提供了以下通用的内存分配: kmalloc(),kzalloc(),kmalloc_array(),kcalloc(),vmalloc(),和 vzalloc()。有关它们的更多信息,请参阅 API 文档。 内存分配指南

传递结构大小的首选形式如下:

p = kmalloc(sizeof(*p), …);
当指针变量类型更改但传递给内存分配器的相应 sizeof 未更改时,拼写结构名称的替代形式会损害可读性并引入错误的机会。

强制转换为空指针的返回值是多余的。C 编程语言保证了从 void 指针到任何其他指针类型的转换。

分配数组的首选形式如下:

p = kmalloc_array(n, sizeof(…), …);
分配归零数组的首选形式如下:

p = kcalloc(n, sizeof(…), …);
两种形式都检查分配大小 n * sizeof(…) 上的溢出,如果发生则返回 NULL。

这些通用分配函数在没有 __GFP_NOWARN 的情况下使用时都会在失败时发出堆栈转储,因此在返回 NULL 时发出额外的失败消息是没有用的。

15 、内联

似乎有一个普遍的误解,即 gcc 有一个神奇的“让我更快”的加速选项,称为inline. 虽然内联的使用可能是合适的(例如作为替换宏的方法,参见第 12 章),但通常不是。大量使用 inline 关键字会导致更大的内核,这反过来又会降低整个系统的速度,因为 CPU 的 icache 占用空间更大,而且页面缓存可用的内存更少。考虑一下; 页面缓存未命中会导致磁盘搜索,这很容易花费 5 毫秒。有很多 cpu 周期可以进入这 5 毫秒。

一个合理的经验法则是不要将内联放在包含超过 3 行代码的函数中。此规则的一个例外是参数已知为编译时常量的情况,并且由于此常量,您知道编译器将能够在编译时优化大部分函数。有关后面这种情况的一个很好的例子,请参阅kmalloc()内联函数。

人们经常争辩说,向静态且仅使用一次的函数添加内联总是一种胜利,因为没有空间权衡。虽然这在技术上是正确的,但 gcc 能够在没有帮助的情况下自动内联这些,并且当第二个用户出现时删除内联的维护问题超过了告诉 gcc 做一些它无论如何都会做的事情的提示的潜在价值。

16、函数返回值和名称

函数可以返回多种不同类型的值,最常见的一种是指示函数是成功还是失败的值。这样的值可以表示为错误代码整数(-Exxx = 失败,0 = 成功)或succeeded布尔值(0 = 失败,非零 = 成功)。

混合这两种表示是难以发现的错误的肥沃来源。如果 C 语言包含整数和布尔值之间的明显区别,那么编译器会为我们发现这些错误……但它没有。为了帮助防止此类错误,请始终遵循以下约定:

If the name of a function is an action or an imperative command,
the function should return an error-code integer. If the name
is a predicate, the function should return a “succeeded” boolean.
例如,是一个命令,add_work() 函数返回 0 表示成功或 -EBUSY 表示失败。同样,是谓词,如果成功找到匹配的设备,则函数返回 1,否则返回 0。add workPCI device presentpci_dev_present()

所有导出的函数都必须遵守这个约定,所有公共函数也应该如此。私有(静态)函数不需要,但建议他们这样做。

返回值是计算的实际结果而不是计算是否成功的指示的函数不受此规则的约束。通常,它们通过返回一些超出范围的结果来指示失败。典型的例子是返回指针的函数;他们使用 NULL 或 ERR_PTR 机制来报告失败。

17、 使用布尔值

Linux 内核 bool 类型是 C99 _Bool 类型的别名。bool 值只能计算为 0 或 1,隐式或显式转换为 bool 会自动将值转换为 true 或 false。使用 bool 类型时 !! 不需要构造,这就消除了一类错误。

使用 bool 值时,应使用 true 和 false 定义而不是 1 和 0。

bool 函数返回类型和堆栈变量总是可以在适当的时候使用。鼓励使用 bool 来提高可读性,并且对于存储布尔值通常是比 ‘int’ 更好的选择。

如果缓存行布局或值的大小很重要,请不要使用 bool,因为它的大小和对齐方式根据编译的架构而变化。针对对齐和大小优化的结构不应使用 bool。

如果结构具有许多真/假值,请考虑将它们合并为一个具有 1 位成员的位域,或使用适当的固定宽度类型,例如 u8。

类似地,对于函数参数,许多真/假值可以合并为单个按位“标志”参数,如果调用站点具有裸真/假常量,“标志”通常可以是更具可读性的替代方案。

否则,在结构和参数中有限使用 bool 可以提高可读性。

18、不要重新发明内核宏

头文件 include/linux/kernel.h 包含许多您应该使用的宏,而不是自己明确编码它们的某些变体。例如,如果您需要计算数组的长度,请利用宏

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
同样,如果您需要计算某个结构成员的大小,请使用

#define sizeof_field(t, f) (sizeof(((t*)0)->f))
如果需要,还有 min() 和 max() 宏可以进行严格的类型检查。随意仔细阅读该头文件以查看已经定义的其他内容,您不应该在代码中重现。

19、编辑器模式和其他杂物

一些编辑器可以解释嵌入在源文件中的配置信息,用特殊标记表示。例如,emacs 解释如下标记的行:

-- mode: c --
或者像这样:

/*
Local Variables:
compile-command: “gcc -DMAGIC_DEBUG_FLAG foo.c”
End:
*/
Vim 解释如下标记:

/* vim:set sw=8 noet */
不要在源文件中包含任何这些。人们有自己的个人编辑器配置,您的源文件不应覆盖它们。这包括用于缩进和模式配置的标记。人们可能会使用他们自己的自定义模式,或者可能有一些其他神奇的方法来使缩进正常工作。

  1. 内联组装
    在特定于体系结构的代码中,您可能需要使用内联汇编来与 CPU 或平台功能交互。必要时不要犹豫,这样做。但是,当 C 可以完成这项工作时,不要无缘无故地使用内联汇编。如果可能,您可以并且应该从 C 中插入硬件。

考虑编写简单的辅助函数来包装内联汇编的常见位,而不是重复编写带有细微变化的它们。请记住,内联汇编可以使用 C 参数。

大型的、非平凡的汇编函数应该放在 .S 文件中,并在 C 头文件中定义相应的 C 原型。汇编函数的 C 原型应该使用asmlinkage.

您可能需要将 asm 语句标记为 volatile,以防止 GCC 在 GCC 没有注意到任何副作用时将其删除。但是,您并不总是需要这样做,并且不必要地这样做会限制优化。

在编写包含多条指令的单个内联汇编语句时,将每条指令放在单独的一行中单独的带引号的字符串中,并结束除最后一个字符串之外的每个字符串\n\t以在汇编输出中正确缩进下一条指令:

asm (“magic %reg1, #42\n\t”
“more_magic %reg2, %reg3”
: /* outputs / : / inputs / : / clobbers */);
21) 条件编译
在可能的情况下,不要在 .c 文件中使用预处理器条件(#if、#ifdef);这样做会使代码更难阅读,逻辑更难遵循。相反,在定义用于那些 .c 文件的函数的头文件中使用此类条件,在 #else 情况下提供无操作存根版本,然后从 .c 文件无条件调用这些函数。编译器将避免为存根调用生成任何代码,产生相同的结果,但逻辑仍将易于遵循。

更喜欢编译整个函数,而不是部分函数或部分表达式。与其将 ifdef 放入表达式中,不如将部分或全部表达式分解为单独的辅助函数并将条件应用于该函数。

如果您有一个函数或变量可能在特定配置中未使用,并且编译器会警告其定义未使用,请将定义标记为 __maybe_unused 而不是将其包装在预处理器条件中。(但是,如果函数或变量总是未使用,请将其删除。)

在代码中,在可能的情况下,使用 IS_ENABLED 宏将 Kconfig 符号转换为 C 布尔表达式,并在普通 C 条件中使用它:

if (IS_ENABLED(CONFIG_SOMETHING)) {

}
编译器会将条件折叠起来,并像#ifdef 一样包含或排除代码块,因此这不会增加任何运行时开销。但是,这种方法仍然允许 C 编译器查看块内的代码,并检查它的正确性(语法、类型、符号引用等)。因此,如果块内的代码引用了不满足条件时将不存在的符号,则您仍然必须使用 #ifdef。

在任何非平凡的#if 或#ifdef 块(多于几行)的末尾,在同一行的#endif 之后放置一个注释,注意使用的条件表达式。例如:

#ifdef CONFIG_SOMETHING

#endif /* CONFIG_SOMETHING */
附录一)参

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arm精选

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值