编码风格之(2)C语言规范(Linux内核风格)

Linux内核C语言编码风格指南 C语言作为系统编程的核心语言,其编码规范直接影响代码的可读性和可维护性。Linux内核作为全球最大的开源项目之一,其独特的编码风格经过30多年演进,形成了以实用主义为导向的工程实践标准。从基础语法规则到高级设计模式,内核编码规范强调简洁性、一致性和明确性,使用8字符制表符缩进、80字符行宽限制等具体措施保证代码质量。这些规范不仅适用于内核开发,对嵌入式系统、驱动程序等底层软件开发同样具有参考价值。通过checkpatch.pl等自动化工具,开发者可以快速验证代码是否符合Linux内核标准,提升团队协作效率。 阅读详情

编码风格之(2)C语言规范(Linux内核风格)

Author:Once Day Date:2023年12月27日

漫漫长路,才刚刚开始…

本文档主要内容翻译于《Linux kernel coding style》

配套的clang-format配置可参考文档:

1. 概述

本文总结并且描述Linux内核的编码风格,这是一份存在很久的文档,其实每一种编码风格都有其侧重点,也不会面面俱到,对于我们来说,不妨放下内心的偏见,博纳众家之长,丰富认知和经验。

这是一个简短的文档,描述了linux内核的首选编码风格。编码风格是非常个人化的,我不会把我的观点强加给任何人,但这是我必须能够维护的任何东西,我也希望它适用于大多数其他东西。请至少考虑一下这里提出的观点。首先,我建议打印一份GNU编码标准的副本,不要读它。烧了它们,这是一个很好的象征性姿态。

原文档开篇非常有趣,准备烧掉GNU编码规范,在我看来,就是需要尊重已有的编码风格,不能拿其他地方或者个人经验去搪塞。对于大型C语言项目来说,整齐划一的风格,对于维护和学习,有很大的帮助。

这是一个持续的学习过程,并非一朝一夕之事,如果哪天编码时,有那么一丝疑惑,不妨拿出来看看。

后续的内容,直接机翻于英文文档,中间添加了一些注释说明,描述了个人的一些理解。

2. 文件类风格
2.1 代码缩进

制表符是8个字符,因此缩进也是8个字符。有些异端运动试图将缩进深度设置为4(甚至2!)个字符,这类似于试图将PI的值定义为3。

理由:缩进背后的整个想法是清楚地定义控制块的开始和结束位置。特别是当你连续盯着屏幕20个小时的时候,你会发现如果你有大的缩进,你会更容易看到缩进是如何工作的。

现在,有些人会声称8个字符的缩进会使代码向右边偏移太多,而且在80个字符的终端屏幕上很难阅读。答案是,如果如果你需要超过3层的缩进,你就完蛋了,应该修复你的程序。

(注: 现在惯例是使用space缩进,而不是tab,并且缩进以4个字符为单位,tab也等价解释为4个空白字符)

简而言之,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;
        fallthrough;
default:
        break;
}

(注: switch风格建议case和switch同一列,并且标明fallthrough的情况)

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

if (condition) do_this;
  do_something_everytime;

不要使用逗号来避免使用大括号:

if (condition)
        do_this(), do_that();

总是对多个语句使用大括号:

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

也不要将多个任务放在一行上。内核编码风格超级简单。避免复杂的表达。除了注释、文档和Kconfig之外,空格从来不用于缩进,上面的例子是故意打破的。找一个好的编辑器,不要在行尾留空格。

2.2 把长的行和字符串打散

编码风格是指在使用常见工具时,能具备可读性和可维护性。

单行长度的首选限制是80列。超过80列的语句应该分成合理的块,除非超过80列会显著提高可读性并且不会隐藏信息。

子参数总是比父函数短得多,并且被放置在右边。一种非常常用的样式是将子参数与函数的左括号对齐。这些相同的规则适用于具有长参数列表的函数头文件。

但是,永远不要破坏用户可见的字符串,如printk消息,因为这会破坏对它们进行grep的能力。

2.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中嵌套它们)。

请注意,右括号在它自己的一行上是空的,除非它后面跟着同一语句的延续,即do语句中的while或if语句中的else,例如下面这样的:

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

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

另外,请注意,这种大括号的放置还减少了空(或几乎空)行的数量,而不会损失可读性。因此,由于屏幕上的新行不是可再生资源(想想这里的25行终端屏幕),您有更多的空行可以放置注释。

在单个语句就可以完成的地方,不要不必要地使用大括号。

(注: 当前惯例一般是不再省略大括号,因为容易出隐形BUG,毕竟现在的屏幕非常大,远不止25行)

if (condition)
        action();

if (condition)
        do_this();
else
        do_that();

如果条件语句只有一个分支是单个语句,则不适用; 在后一种情况下,在两个分支中使用大括号:

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

同样,当循环包含多个简单语句时,使用大括号:

while (condition) {
        if (test)
                do_something();
}
2.4 如何放置空白字符

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

在大多数二元和三元操作符(两边)周围使用一个空格,例如以下任何一个:

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

但是一元操作符(unary operators)后面没有空格:

&  *  +  -  ~  !  sizeof  typeof  alignof  __attribute__  defined

后置和前缀自增和自减一元操作符前没有空格:

++  --

在.和->结构体成员操作符周围没有空白字符:

book.name
book_p->name

不要在行尾留下尾随空格。一些带有智能缩进的编辑器会在新行开头适当地插入空白,因此您可以立即开始键入下一行代码。但是,如果您最终没有在那里放置一行代码,例如如果您留下空白行,则某些此类编辑器不会删除空白。因此,您最终会看到包含尾随空格的行。

Git会警告你关于引入尾随空格的补丁,并可以选择性地为你删除尾随空格;但是,如果应用一系列补丁,这可能会使该系列中的后续补丁由于更改其上下文行而失败。

3. 语言类风格
3.1 如何命名

C是一种简朴语言,您的命名约定应该遵循它。

与Modula-2和Pascal程序员不同,C程序员不会使用像ThisVariableIsATemporaryCounter这样可爱的名字。C程序员会把这个变量称为tmp,这样写起来容易得多,而且理解起来一点也不困难。

然而,尽管混合大小写的名称是不受欢迎的,但全局变量的描述性名称是必须的。将全局函数调用为foo是一种不合适的行为。全局变量(仅在真正需要时使用)需要具有描述性名称,就像全局函数一样。如果您有一个计算活动用户数量的函数,您应该调用count_active_users()或类似的函数,而不应该调用cntusr()。

将函数的类型编码到名称中(所谓的匈牙利符号)是愚蠢的,编译器无论如何都知道这些类型,并且可以检查这些类型,这只会使程序员感到困惑。

局部变量名应该简短,切中要害。如果你有一些随机的整数循环计数器,它可能应该被称为i。如果没有被误解的机会,调用它loop_counter是没有实际意义的。类似地,tmp可以是用于保存临时值的任何类型的变量。

如果你害怕混淆你的局部变量的名字,你有另一个问题,这被称为函数增长激素失衡综合症,即一个函数包含了太多功能,以至过于臃肿。

对于符号名称和文档,避免引入新的master/slave(或独立于master的slave)和blacklist/whitelist。

  • master/slave推荐替换为:

    '{primary,main} / {secondary,replica,subordinate}'
    '{initiator,requester} / {target,responder}'
    '{controller,host} / {device,worker,proxy}'
    'leader/follower'
    'director/performer'
    
  • “blacklist/whitelist”推荐替换为:

    'denylist/allowlist'
    'blocklist/passlist'
    

引入新用法的例外情况是维护用户空间ABI/API,或者在更新现有(截至2020年)硬件或协议规范的代码时强制使用这些术语。对于新的规范,尽可能将术语的规范用法转换为内核编码标准。

3.2 类型定义

请不要使用vps_t之类的东西。对结构体和指针使用typedef是错误的。当你看到一个:

vps_t a;

在原文中,它是什么意思? 相反,如果它说:

struct virtual_container *a;

你可以知道a是什么。很多人认为typedefs有助于可读性,不是这样的,它们仅对以下情况有用:

  • 完全不透明的对象(主动使用typedef来隐藏对象是什么)。例如:pte_t等不透明对象,您只能使用适当的访问器函数访问。

    注意: 不透明性和访问器函数本身并不好。我们将它们用于pte_t等内容的原因是,那里确实绝对没有可共同访问的信息。

  • 清晰的整数类型,抽象有助于避免混淆是int还是long,比如u8/u16/u32是非常好的类型。

    注意: 再说一遍,这需要一个理由。如果某个东西是unsigned long,那么就没有理由这样做Typedef unsigned myflags_t;

    但是,如果有明确的理由说明为什么它在某些情况下可能是unsigned int,而在其他配置下可能是unsigned long,那么无论如何都要使用typedef。

  • 当您使用spare(一个设计用于在Linux内核代码中查找可能的编码错误的软件工具)来创建一个新的类型进行类型检查时。

  • 在某些特殊情况下,与标准C99类型相同的新类型。虽然眼睛和大脑只需要很短的时间就能适应像uint32_t这样的标准类型,但有些人还是反对使用它们。因此,linux特定的u8/u16/u32/u64类型及其与标准类型相同的带符号的等价类型是允许的。

    尽管它们在您自己的新代码中不是强制性的。当编辑已经使用一组或另一组类型的现有代码时,您应该遵循该代码中的现有选择。

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

也许还有其他情况,但规则基本上应该是永远不要使用typedef,除非你能清楚地匹配这些规则之一。一般来说,指针或结构体的元素可以合理地直接访问,则不应该使用typedef定义。

3.3 函数风格

函数应该短小精悍,只做一件事。它们应该适合一个或两个屏幕的文本(ISO/ANSI屏幕尺寸是80x24,我们都知道),并做一件事,把它做好。

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

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

函数的另一个度量是局部变量的数量。它们不应该超过5-10个,否则你就做错了。重新思考这个功能,把它分成更小的部分。人类的大脑通常可以很容易地记住大约7件不同的事情,如果多了,它就会感到困惑。你知道你很聪明,但也许你想知道两周后你做了什么。

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

int system_is_up(void)
{
        return system_state == SYSTEM_RUNNING;
}
EXPORT_SYMBOL(system_is_up);
3.4 函数声明/定义风格

在函数原型中,包括参数名和它们的数据类型。尽管C语言不需要这样做,但在Linux中首选这样做,因为这是为读者添加有价值信息的简单方法。

不要在函数声明中使用extern关键字,因为这会使行变长,并且不是严格必要的。在编写函数原型时,请保持元素的顺序有规律。例如,使用这个函数声明示例:

__init 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
{
       ...
}

推荐的函数原型元素顺序是:

  • 储存类型(如 static __always_inline ,注意 __always_inline 技术上来讲是个属性但被当做 inline )
  • 储存类型属性(如 __init,即节声明,但也像 __cold )
  • 返回类型(如 void * )
  • 返回类型属性(如 __must_check )
  • 函数名(如 action )
  • 函数参数(如 (enum magic value, size_t size, u8 count, char *fmt, ...) , 注意必须写上参数名)
  • 函数参数属性(如 __printf(4, 5) )
  • 函数行为属性(如 __malloc )
3.5 函数goto退出风格

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

当函数从多个位置退出并且需要执行一些常见的工作(如清理)时,goto语句会派上用场。如果不需要清理,那么直接返回即可。

选择标签名称,说明goto的作用或goto存在的原因。一个好名字的例子可以是out_free_buffer: 如果goto释放缓冲区。避免使用像err1:和err2:这样的GW-BASIC名称,因为如果要添加或删除退出路径,就必须重新编号,而且难以验证它们的正确性。

使用gotos的基本理由是:

  • 无条件语句更容易理解。
  • 减少了嵌套。
  • 防止在进行修改时不更新个别退出点而导致的错误。
  • 节省了编译器优化冗余代码的工作。
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;
}

需要注意的一种常见错误类型是如下所示的错误:

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;

理想情况下,您应该模拟错误以测试所有退出路径。

3.6 注释风格

注释是好的,但也有过度注释的危险。永远不要试图在注释中解释你的代码模型是如何工作的: 最好把代码写得很明显,解释写得不好的代码是浪费时间。

一般来说,你希望你的注释告诉你代码做了什么,而不是如何做。此外,尽量避免在函数体中放置注释: 如果函数非常复杂,以至于需要对其中的部分进行单独注释,那么应该考虑是否函数过于臃肿了。

你可以对一些特别聪明(或丑陋)的事情做些小的注释或警告,但尽量避免过度。相反,将注释放在函数的头部,告诉人们它做了什么,以及为什么这样做。

注释内核API函数时,请使用kernel-doc格式。详细信息请参见Documentation/doc-guide/和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.
 */

注释数据也很重要,无论它们是基本类型还是派生类型。为此,每行只使用一个数据声明(多个数据声明不使用逗号)。这样你就可以在每件物品上留下小小的注释,解释它的用法。

3.7 数据结构使用

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

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

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

当有不同类的用户时,许多数据结构确实可以有两层引用计数。子类计数计算子类用户的数量,当子类计数为零时,全局计数只减少一次。这种多级引用计数的例子可以在内存管理(结构体mm_struct: mm_users和mm_count)和文件系统代码(结构体super_block: s_count和s_active)中找到。

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

3.8 宏和枚举

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

#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)
    
  • 依赖于具有魔术名称的局部变量的宏,可能看起来是一件好事,但当人们阅读代码时,它就像地狱一样令人困惑,并且容易因看似无害的更改而损坏。

    #define FOO(val) bar(index, val)
    
  • 带有参数作为左值的宏: FOO(x) = y;如果有人把FOO变成内联函数,就会咬你一口。

  • 忘记优先级: 使用表达式定义常量的宏必须将表达式括起来。注意使用参数的宏的类似问题。

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

    #define FOO(x)                          \
    ({                                      \
            typeof(x) ret;                  \
            ret = calc_ret(x);              \
            (ret);                          \
    })
    

    ret是局部变量的通用名称,_foo_ret不太可能与现有变量冲突。

3.9 申请内存

内核提供了以下通用内存分配器:kmalloc(),kzalloc(),Kmalloc_array(),kcalloc(),vmalloc(),vzalloc()。请参考API文档关于他们的更多信息。文档/核心api/memory-allocation.rst。

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

p = kmalloc(sizeof(*p), ...);

另一种形式是将struct名称拼写出来,这种形式会损害可读性,并且在指针变量类型被更改,但相应的sizeof没有传递给内存分配器时,可能会引入错误。

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

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

p = kmalloc_array(n, sizeof(...), ...);

分配一个归零数组的首选形式如下:

p = kcalloc(n, sizeof(...), ...);

这两种形式检查分配大小n * sizeof(…)是否溢出,如果发生则返回NULL。当不使用_GFP_NOWARN时,这些通用分配函数都会在失败时发出堆栈转储,因此在返回NULL时没有必要发出额外的失败消息。

3.10 内联函数

似乎有一个常见的误解,gcc有一个神奇的“让我更快”的加速选项称为内联。虽然使用内联是合适的(例如,作为替换宏的一种手段),但通常不是这样。

大量使用内联关键字会导致更大的内核,这反过来又降低了整个系统的速度,因为CPU的icache占用更大,而且用于页面缓存的可用内存更少。想想看,pagecache缺失会导致磁盘寻道,这通常需要5毫秒,在这5毫秒中有很多cpu周期。

一个合理的经验法则是不要使用代码超过3行的内联函数。这条规则的例外情况是,参数已知是编译时常数,并且由于这种常数,您知道编译器将能够在编译时优化您的大部分函数。

有关后一种情况的一个好例子,请参阅kmalloc()内联函数。人们经常认为,向静态且只使用一次的函数中添加内联总是一种胜利,因为没有空间被浪费。

虽然这在技术上是正确的,但gcc能够在没有帮助的情况下自动内联这些内容。而且其他用户可能会要求移除 inline,由此而来的争论会抵消 inline 自身的潜在价值,得不偿失。

3.11 函数返回值

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

将这两种表示混合在一起会产生难以发现的bug。如果C语言包含整数和布尔值之间的强烈区别,然后编译器会为我们找到这些错误……但事实并非如此。为了防止此类错误,请始终遵循以下约定:

  • 如果函数的名称是动作或命令式命令,则该函数应返回一个错误码整数。
  • 如果名称是谓词,则函数应该返回一个“成功”的布尔值。

例如,add work是一个命令,add_work()函数返回0表示成功,返回-EBUSY表示失败。以同样的方式,PCI device present是一个谓词,如果pci_dev_present()函数成功找到匹配的设备,则返回1,否则返回0。

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

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

3.12 bool类型

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

当使用bool值时,应该使用true和false定义,而不是1和0。Bool函数的返回类型和堆栈变量总是可以在适当的时候使用。鼓励使用bool来提高可读性,并且对于存储布尔值来说,bool通常是比int更好的选择。

如果缓存栈布局或值的大小很重要,不要使用bool,因为它的大小和对齐方式基于编译的架构,因此往往具有较大差异。对对齐和大小进行优化的结构不应该使用bool。

如果一个结构体有很多真/假值,考虑将它们合并到一个1位成员的位域中,或者使用一个合适的固定宽度类型,比如u8。

类似地,对于函数参数,许多true/false值可以合并到单个按位使用的flags参数中,如果调用点具有裸true/false常量,则flags通常是更具可读性的替代方案。

总之,在结构体和参数中有限地使用bool可以提高可读性。

3.13 内联汇编

在特定于体系结构的代码中,可能需要使用内联汇编与CPU或平台进行对接功能,必要时不要犹豫。

但是,当C可以完成这项工作时,不要随意使用内联汇编。在可能的情况下,您可以并且应该从C中获取硬件数据。考虑编写简单的helper函数来包装内联汇编的公共位,而不是稍有变化就反复地写新函数。

请记住,内联汇编可以使用C参数。大型的、重要的汇编函数应该放在.s文件中,在C头文件中定义相应的C原型。汇编函数的C原型应该使用asmlinkage。

你可能需要把汇编语句标记为 volatile,用来阻止 GCC 在没发现任何副作用后就把它移除了。你不必总是这样做,尽管,这不必要的举动会限制优化。

在写一个包含多条指令的单个内联汇编语句时,把每条指令用引号分割而且各占一行, 除了最后一条指令外,在每个指令结尾加上 \n\t ,让汇编输出时可以正确地缩进下一条指令:

asm ("magic %reg1, #42\n\t"
     "more_magic %reg2, %reg3"
     : /* outputs */ : /* inputs */ : /* clobbers */);
3.14 条件编译

只要可能,不要在.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 */
4. 杂项
4.1 kconfig风格

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

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。

4.2 打印内核信息

内核开发人员喜欢被看作是有文化的。一定要注意内核消息的拼写,给人留下好印象。不要使用不正确的缩略词,比如don,用do not或don'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…)。

4.3 不要重新发明内核宏

头文件include/linux/kernel.h包含许多应该使用的宏,而不是自己显式地编写它们的一些变体。例如,如果需要计算数组的长度,可以利用宏:

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

类似地,如果需要计算某个结构成员的大小,使用:

#define sizeof_field(t, f) (sizeof(((t*)0)->f))

如果需要的话,还有min()和max()宏可以执行严格的类型检查。请随意阅读该头文件,看看还有哪些已经定义但不应该在代码中复制的内容。

4.4 编辑器配置参数

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

-*- mode: c -*-

或者如下:

/*
Local Variables:
compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
End:
*/

Vim解析如下所示的标记:

/* vim:set sw=8 noet */

不要在源文件中包含这些内容。每个人都有自己的编辑器配置,所以你的源文件不应该覆盖它们。这包括用于缩进和模式配置的标记。人们可能会使用自己的自定义模式,或者可能有一些其他神奇的方法来使缩进正确工作。

4.5 内核错误处理

一般来说,使内核崩溃的决定权属于用户,而不是内核开发人员。

避免使用panic()函数,应该谨慎使用panic(),并且主要只在系统引导期间使用。例如,当启动过程中内存耗尽并且无法继续运行时,Panic()是可以接受的。

使用WARN()宏而不是BUG()宏,不要添加使用任何BUG()变体的新代码,例如BUG()、BUG_ON()或VM_BUG_ON()。相反,使用WARN*()变体,简称为WARN_ON_ONCE(),并可能带有恢复代码。如果没有合理的方法至少部分恢复,则不需要恢复代码。“我太懒了,不想处理错误”不是使用BUG()的借口。如果内部错误在没有办法继续的情况下,可以仍然使用BUG(),但需要充分的理由。

使用WARN_ON_ONCE()而不是WARN()或者WARN_ON(),WARN_ON_ONCE()通常优于WARN()或WARN_ON(),因为对于给定的警告条件,如果发生的话,通常会发生多次。这可能会填满和包装内核日志,甚至可能使系统变慢,以至于过多的日志记录变成了它自己的额外问题。

不要轻易的使用WARN宏,WARN*()用于意外的、不应该发生的情况。WARN*()宏不能用于正常操作期间预期发生的任何事情。例如,这些不是前置或后置条件断言。同样,不能将WARN*()用于预期容易触发的条件,例如,由用户空间操作触发。如果需要将问题通知用户,pr_warn_once()是一个可能的替代方法。

不要担心panic_on_warn用户,关于panic_on_warn再多说几句,请记住,panic_on_warn是一个可用的内核选项,许多用户都设置了这个选项。这就是为什么上面写着“不要轻易警告”的原因。但是,panic_on_warn用户的存在并不是避免明智地使用WARN*()的有效理由。这是因为,启用panic_on_warn的人已经显式地要求内核在WARN*()触发时崩溃,这样的用户必须准备好去面对更有可能崩溃的系统和处理其带来的后果。

对编译时断言使用BUILD_BUG_ON(),使用BUILD_BUG_ON()是可以接受和鼓励的,因为它是一个编译时断言,在运行时没有影响。

附录:
附录.1 sparse工具介绍

Sparse是一个设计用于在Linux内核代码中查找可能的编码错误的软件工具。Linus Torvalds启动了Sparse的开发,以帮助对Linux内核代码进行静态分析。它可以检查包括类型安全性在内的多种问题。

Sparse的一个功能是定义新的类型,这些类型即使可能基于相同的底层C数据类型,但与其他类型是不同的。这些被称为“不透明类型”。这在内核中很有用,可以防止混淆不同类型的句柄或标识符。

例如,你可能有多种在内核中由整数表示的标识符:

  • 文件描述符
  • 进程ID
  • 内存地址

如果没有额外的类型检查,C语言中没有什么能阻止程序员不小心将文件描述符传递给期望进程ID的函数,因为它们都是由整数表示的。

Sparse允许创建新的类型,这样如果有人试图在期望另一个类型的地方使用一个类型,编译器(通过Sparse的检查)会报告警告。这种类型检查比标准C提供的更加严格,它有助于防止错误地将错误类型的值传递给函数的错误。

这里是一个如何用Sparse定义新类型的例子:

typedef int __attribute__((noderef, address_space(1))) my_new_type;

在这行代码中,__attribute__((noderef, address_space(1)))是一个Sparse特定的属性,告诉Sparse这是一个在单独地址空间中的新类型的指针类型,而my_new_type是一个与普通整数不同的新类型,用于类型检查。

使用这些Sparse注解,Linux内核开发者可以创建一个类型系统,捕捉到C语言本身不会捕捉到的某些类型的错误。Linux内核编码风格文档可能包含如何使用Sparse及其注解来提高内核代码安全性和可靠性的指导原则。

【C语言】C 语言编码风格(基于 ISO/IEC 9899:2024 和 Linux 内核编码风格 v6.13.0-rc4) C 语言的编码风格标准在许多方面有相似之处,特别是在变量命名、函数定义、注释和代码结构方面。**ISO/IEC 9899:2024 (N3220)**(即 C23 标准)提供了一些通用的编码建议,而 **Linux 内核编码风格** 则更多关注内核代码的性能和可维护性。 阅读详情

相关推荐

Linux内核编码风格

这是一份简短的,描述linux内核首选编码风格的文档。编码风格是很个人化的东西,而且我也不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格,并且我也希望绝大多数其他代码也能遵守这个风格。所以请至少考虑一下本文所述的观点。

Linux下C语言编程风格和规范

Linux有独特的编程风格,在内核源代码目录Documentation/CodingStyle,详细描述代码风格。 建议大家可以去看一下,老外写技术文档还是很有意思的,上来就狂喷,“你不这样写就会完蛋,异教徒才不这样写……”,没有国内那么刻板,多阅读英语文档对技术增长很有帮助。

闪耀大叔的博客 3219

C安全编码标准-CERT C Programming Language Secure Coding Standard

This document represents a preliminary draft of the CERT C Programming Language Secure Coding Standard. This project was initiated following the 2006 Berlin meeting of WG14 to produce a secure coding standard based on the C99 standard. Although this is an incomplete work, we would greatly appreciate your comments and feedback at this time to further the development and refinement of the material. Please provide comments that are commensurate with the existing detail in the document. For example, if a rule or recommendation is simply a stub you may wish to comment if you think having a rule or recommendation in that area is unwarranted.

《Linux 内核编码风格》官方手册太肝了

关注、星标公众号,直达精彩内容导读:写内核代码时请至少考虑本文提出的风格。                                         本文字数:21559,阅读时长大约:30分钟https://gitee.com/lioneie/blog/blob/master/coding-style-%E7%BF%BB%E8%AF%91-Linux%E5%86%85%E6%A0%B8Co...

李肖遥的专栏 726

Linux内核编码规范.txt

Linux内核编码规范。 规定了编程风格!

【180609】剑侠情缘_整套源码+地图编辑器(单机学习例子)

剑侠情缘_整套源码+地图编辑编(单机学习例子)完整源码下载地址:点击下载备用下载地址:点击下载

资源小站 6278

学习Linux的编码风格

  对于编码,每个码农或许都会有自己的一套风格,很多人可能对编码风格压根就不关心,因为最终编译器编译出来的目标代码并不会受影响。但是在开发一个大型项目时,花费时间成本最多的永远是开发者们之间的沟通与交流。保持编码风格的一致性可以在很大程度上提高编码效率,可以让其他人更容易读懂你的代码,也让你更容易读懂其他人的代码。   在选择编码风格上,个人更青睐于Linux的编码风格,所以特意总结了...

weixin_30675967的博客 174

浅显易懂地看LLC变换器(三)

第3期 谐振与增益的定性关系 由简单的谐振电路说起 我们将以电感和电容的基本电路的特性逐渐了解谐振电路的特性 与电阻不同,电感和电容都不是纯阻性线性器件,电感的感抗XL和电容的容抗Xc都与频率有关,当加在电感和电容上的频率发生变化时,它们的感抗XL和容抗Xc会发生变化。 1) 如下图RL电路,当输入源Vin的频率增加时,电感的感抗增大,输出电压减小,增益Gain=Vo/Vin随频率增加而减小。 2) 如下图RC电路,相反,当输入源Vin的频率增加时...

iFTrue电力电子专栏 6739

C语言基础:附录:C语言编码规范(如Linux内核风格)

通过严格遵循内核编码规范,可使项目代码具备工业级质量,建议使用自动化工具持续维护规范一致性。:不超过2屏(约80行)后缀保留给POSIX。

赵鑫亿的博客 1236

AFSIM仿真系统组件开发入门:手把手教你扩展传感器与武器模型

本文为AFSIM仿真系统组件开发入门指南,手把手教你如何扩展传感器与武器模型。通过深入解析AFSIM基于组件的体系结构,并结合构建自定义电子扫描阵列雷达传感器和协同交战智能武器系统两个实战案例,详细介绍了从C++插件开发、MATLAB算法集成到脚本接口暴露的完整流程,帮助你掌握定制AFSIM仿真的核心能力。

t8u9v0w1x的博客 489

树莓派4B学习笔记一

树莓派4b烧录Ubuntu20.04&&配置Ubuntu桌面

m0_55202222的博客 3642

Linux内核编码风格及检测脚本与格式化工具

Linux内核的编码风格是我们在编写内核代码或者提交patch时需要参考遵守的一套规范,它不仅可以增强代码的可读性和可维护性,还可以更好的确保代码质量以及方便团队协作。本文我们将详细介绍Linux内核编码风格的一些基本要点,并探讨如何使用检测脚本checkpatch.pl以及C代码格式化工具indent来确保我们的代码更好的符合这些规范。

代码背包客的博客 1476

Windows平台MuJoCo仿真环境配置与Qt集成实战

本文详细介绍了在Windows平台上配置MuJoCo物理仿真环境并与Qt框架集成的完整实战流程。内容涵盖从环境准备、库文件配置、项目创建到核心代码编写的每一步,重点解决了Windows下常见的编译链接错误和路径问题,帮助开发者在Windows系统中高效搭建机器人仿真与可视化控制开发环境。

weixin_29221753的博客 172

C语言入门(九)——编码风格

代码风格好不好就像字写得好不好看一样,如果一个公司招聘秘书,肯定不要字写得难看的,同理,代码风格糟糕的程序员肯定也是不称职的。虽然编辑器不会挑剔难看的代码,照样能编译通过,但是和你一个Team的其他程序员肯定受不了,你自己也受不了,写完代码几天之后再来看,自己都不知道自己写的是什么。代码主要为了写给人看的,而不是给机器看的,只是顺便也能用机器执行而已,如果是为了写给机器看那直接写机器指令就好了,没必要用高级语言了。代码和语言文字一样是为了表达思想,记载信息,所以一定要写的清楚整洁才能有效的表达。

m0_58367586的博客 5067

CATIA二次开发CAA:(一)开发环境搭建

CATIA+CAA+RADE+VS2012

ysm1987的博客 1万+

Windows下CMake的下载与安装详解

目录一、CMake介绍二、CMake的下载与安装 一、CMake介绍 CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。它能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。 二、CMake的下载与安装 CMake下载链接:https://cmake.org/download/。 下载最新稳定版即可(Latest Release): 下载完成后,单击“Next”按钮,在下图中勾选“I acce

Wendy的博客 8万+

Linux-Linux下的C语言编码风格

Linux编码风格 Linux有独特的编码风格,在内核源代码下存在一个文件Documentation/CodingStyle,进行了比较详细的描述。 Linux程序的命名习惯和Windows程序的命名习惯及著名的匈牙利命名法有很大的不同。 ...

DSK_981029的博客 1854

Android Binder的两三事--基于Android14源码

Android14-Framework-Binder

保持专注,保持热爱 1140

awesome-guidelines权威指南:Linux内核编码风格解析

Linux内核作为全球最大规模的开源项目之一,其代码质量一直备受推崇。在awesome-guidelines项目中,Linux内核编码风格指南是众多开发者必学的经典规范。这份权威指南不仅是Linux内核开发者的必备工具,更是所有C语言程序员提升代码质量的重要参考。 ## 📋 Linux内核编码风格概述 Linux内核编码风格是由Linus Torvalds本人亲自编写的,它详细规定了代码格式

gitblog_00471的博客 846

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Linux内核编码风格规范与实践指南

代码规范是软件开发中的基础工程实践,直接影响代码的可读性和可维护性。Linux内核作为全球最大的开源项目,其编码风格规范以实用性和一致性著称,采用8字符制表符缩进和80列行宽限制等特色规则。在函数设计方面强调单一职责原则,合理使用goto进行错误处理,体现了工程实践中的灵活取舍。通过checkpatch.pl等自动化工具保证规范落地,这种将编码规范与工具链深度整合的做法,为大型项目开发提供了重要参考。内核编码哲学中体现的实用主义优先原则,对现代C语言开发具有普遍指导意义。

weixin_30938149的博客 327

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Linux内核编码风格指南:从规范到实践

代码风格规范是软件开发中确保可读性和可维护性的重要基础。在Linux内核开发中,编码风格不仅涉及缩进、空格等基础格式,更体现了Unix设计哲学和工程实践智慧。Linux内核采用8字符tab缩进、80字符行宽限制等独特规范,这些设计既考虑了视觉清晰度,也通过约束条件促使开发者写出更简洁的代码。在函数设计方面,内核推崇短小精悍的实现,并创新性地使用goto进行集中错误处理,这种模式显著提高了资源管理的可靠性。对于C语言开发者而言,理解Linux内核编码规范不仅能提升代码质量,更能学习到大型开源项目的协作经验。掌

weixin_29234423的博客 211

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Linux内核编码风格规范详解与实践指南

编码规范是软件开发中确保代码可读性和可维护性的基础要素,尤其在大规模协作项目中更为关键。Linux内核作为开源项目的典范,其编码风格经过数十年演进形成了一套完整的C语言实践标准。从基础的缩进规则(强制使用8字符制表符)、代码布局(K&R风格大括号)到高级内存管理(kmalloc/vmalloc选择)和错误处理(goto集中清理),这些规范既体现了工程实践智慧,也显著降低了全球开发者协作的认知成本。内核风格的特色在于强调统一性而非灵活性,通过静态检查工具(如checkpatch.pl)和文档系统(kernel

weixin_34009794的博客 362

【质量】编码规范(linux内核编码规范、C编码规范、java编程规范……)

谷歌C++编码规范 https://download.youkuaiyun.com/download/chengbozhang/10318894 1 保持一致也非常重要,如果你在一个文件中新加的代码和原有代码风格相去甚远的话,这就破坏了文件本身的整体美观也影响阅读,所以要尽量避免。 2 一些条目往往有例外,比如下面这些,所以本图不能代替文档,有时间还是把PDF认真阅读一遍吧。 异常......

我的笔记本 1523

linux内核_Linux内核编程风格简介

01不同的语言风格当今世界上一共有5000多种语言。不同的国家、民族和地区,说着不同的语言。每个民族不一定有自己的文字,但一般都会有自己的语言。即使是同一种语言,在不同的时代、不同的地区和群体,说话的方式和语言风格也各不相同。同样是刘备《三顾茅庐》的故事,如果换成不同的人去说,你会发现说话方式和语言风格大相径庭:TVB腔:呐,做人呢,最重要的是开心。玄德你饿不饿,我煮碗面给你吃。日系风:...

weixin_39870238的博客 285

linux内核的编码方式是,linux内核编码风格

Linux内核编码风格这是一份简短的,描述linux内核首选编码风格的文档。编码风格是很个人化的东西,而且我也不愿意把我的观点强加给任何人,不过这里所讲述的是我必须要维护的代码所遵守的风格,并且我也希望绝大多数其他代码也能遵守这个风格。所以请至少考虑一下本文所述的观点。首先,我建议你打印一份GNU的编码规范,然后不要读它。烧掉它,这是一个很高调的具有象征意义的姿态。Anyway, here goe...

weixin_35823441的博客 750
上一篇: 编码风格之(1)C语言建议规范
下一篇: 编码风格之(3)GUN软件标准风格(1)
Once-Day
Once-Day 新星创作者: 运维技术领域 新星创作者: 运维技术领域 优质创作者: C/C++技术领域 优质创作者: C/C++技术领域
博客等级 码龄7年 1万+粉丝 417原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值