[TOC]

属性

__cleanup

__attribute_malloc__ 用于标记函数返回一个新分配的内存块

  • 这有助于编译器进行优化,例如避免不必要的内存检查。

__attribute_alloc_size__ 用于指定分配的内存大小

  • attribute_alloc_size__ ((1)) 是另一个 GCC 特定的扩展,用于指定分配的内存大小。参数 (1) 表示第一个参数 __size 是分配的内存大小。这有助于编译器进行优化和静态分析

attribute((const)) 标记为纯函数(pure function)

纯函数具有以下特性:

  1. 无副作用:纯函数不依赖于任何可变的全局状态,也不会修改任何全局状态。这意味着函数的输出仅依赖于其输入参数,不会对程序的其他部分产生影响。
  2. 可优化:由于纯函数的输出仅依赖于输入参数,编译器可以进行更激进的优化。例如,编译器可以将纯函数的多次调用合并为一次调用,或者在调用纯函数时进行常量传播和代码移动等优化。

attribute((externally_visible)) 使其在编译器优化过程中保持对外部模块的可见性

  • 用于标记函数或变量,使其在编译器优化过程中保持对外部模块的可见性。通常情况下,编译器可能会优化掉未被直接引用的符号(函数或变量),但使用这个属性可以防止这种优化。
  • 作用: 它确保标记的符号在链接时不会被移除,即使编译器认为它没有被使用。这对于动态链接库(shared libraries)或模块化程序设计非常重要,因为其他模块可能需要访问这些符号。

attribute((fallthrough)) 带有空语句的 fallthrough 属性用作穿透语句

  • 带有空语句的 fallthrough 属性用作穿透语句。它向编译器暗示,在 switch 语句中跳转到另一个 case 标签或用户定义标签的语句是故意的,因此不应触发 -Wimplicit-fallthrough 警告。穿透属性在每个属性列表中最多只能出现一次,并且不能与其他属性混合。它只能在 switch 语句中使用(否则编译器将发出错误),在先前的语句之后和逻辑上后续的 case 标签或用户定义标签之前。

__must_check warn_unused_result

  • warn_unused_result属性如果使用该属性的函数调用者不使用其返回值,则会发出警告。这对于不检查结果要么是安全问题,要么总是错误的函数很有用,比如realloc。
1
#define __must_check                    __attribute__((__warn_unused_result__))

noinline 禁止内联函数

  • noinline 是一个 GCC 特定的属性,用于禁止编译器将函数内联。内联函数是一种优化技术,编译器会将函数的代码直接插入到调用该函数的地方,以减少函数调用的开销。然而,在某些情况下,开发人员可能希望禁止内联,以确保函数的行为和性能符合预期。

attribute((noreturn)) 标记函数不会返回到调用者

  • 一些标准库函数,例如 abort 和 exit ,无法返回。GCC 会自动知道这一点。一些程序定义了自己的函数,这些函数永远不会返回。您可以声明它们为 noreturn 来告诉编译器这一事实
  • 关键字 noreturn 告诉编译器假设 fatal 无法返回。然后它可以进行优化,无需考虑如果 fatal 何时返回会发生什么。这使得代码略微更优。更重要的是,它有助于避免未初始化变量的虚假警告。
  • 关键字 noreturn 不会影响当适用时的异常路径:一个标记为 noreturn 的函数仍然可以通过抛出异常或调用 longjmp 返回调用者
  • 一个 noreturn 函数的返回类型不应该是 void

attribute((no_sanitize_address)) 不需要执行内存错误检测

__no_sanitize_address__ 是一个编译器属性,通常用于禁用地址消毒器(Address Sanitizer, ASan)对特定函数或代码段的检查。以下是对其作用和使用场景的详细解释:

  1. 地址消毒器(ASan)简介:
    Address Sanitizer 是一种内存错误检测工具,广泛用于捕获诸如缓冲区溢出、堆栈溢出、使用未初始化内存等问题。它通过在编译时插入额外的检查代码来检测这些错误。然而,这些检查可能会增加运行时开销,或者在某些低级代码中导致误报。

  2. __no_sanitize_address__ 的作用:
    当一个函数或代码段被标记为 __no_sanitize_address__ 时,编译器会跳过对该部分代码的 ASan 检查。这意味着:

    • 该代码不会受到 ASan 的额外检查。
    • 可以避免 ASan 在某些低级操作(如直接操作内存或硬件寄存器)中产生的误报。
    • 减少运行时开销,特别是在性能关键的代码中。

总结

__no_sanitize_address__ 是一个有用的工具,用于在特定场景下禁用 ASan 检查。它在需要直接操作内存、优化性能或避免误报时非常有用。然而,使用时需要谨慎,因为禁用 ASan 检查可能会掩盖潜在的内存错误。

attribute((no_stack_protector))

  • __no_stack_protector__ 是一个 GCC 特定的属性,用于禁用栈保护功能。栈保护是一种安全机制,用于防止栈溢出攻击,例如缓冲区溢出。它通过在函数调用时插入额外的检查代码来检测栈的完整性。
  • 当一个函数被标记为 __no_stack_protector__ 时,编译器将不会为该函数插入栈保护代码。这可能会导致安全风险,因为攻击者可以利用栈溢出漏洞来执行恶意代码。

__no_profile 编译器不应使用与配置文件相关的工具对函数进行操作

  • 在函数声明上使用 no_profile_instrument_function 属性来表示编译器不应使用与配置文件相关的工具对函数进行操作,例如通过 -fprofile-generate / -fprofile-instr-generate / -fcs-profile-generate / -fprofile-arcs 标志来实现。

notrace 禁止跟踪函数调用

1
__attribute__((__no_instrument_function__))

attribute((pure)) 标记为纯函数

这段代码定义了一个名为 __pure 的宏,它使用了 GCC 提供的 __attribute__((__pure__)) 属性,并附带了一个链接,指向 GCC 官方文档中关于纯函数(pure function)的说明。以下是对其的详细解释:

__pure 的定义

__pure 是一个宏,用于简化对 GCC 的 __attribute__((__pure__)) 属性的使用。通过定义宏,开发者可以在代码中更方便地标记函数为“纯函数”,同时提高代码的可读性和可移植性。

什么是纯函数?

纯函数(pure function)是指满足以下条件的函数:

  1. 无副作用:函数不会修改全局状态或影响外部环境(如全局变量、文件、I/O 等)。
  2. 确定性:函数的返回值仅依赖于其输入参数,对于相同的输入参数,函数始终返回相同的结果。

纯函数的这些特性使得它们在编译时更容易被优化。

__pure 的作用

通过为函数添加 __pure 属性,开发者可以显式告诉编译器该函数是一个纯函数。编译器会利用这一信息进行以下优化:

  1. 消除冗余调用
    • 如果函数在同一作用域内被多次调用且输入参数相同,编译器可以只调用一次并缓存结果。
  2. 重新排序指令
    • 由于纯函数没有副作用,编译器可以安全地调整其调用顺序,以优化性能。
  3. 内联优化
    • 编译器可能会更倾向于将纯函数内联,从而减少函数调用的开销。

使用场景

  1. 数学计算

    • 例如,计算平方根、绝对值等函数,这些函数的返回值仅依赖于输入参数。
    1
    2
    3
    __pure int square(int x) {
    return x * x;
    }
  2. 数据转换

    • 将输入数据转换为另一种形式,而不修改全局状态。
    1
    2
    3
    __pure int to_uppercase(char c) {
    return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
    }
  3. 查找操作

    • 在只读数据结构中查找值的函数也可以标记为纯函数。

注意事项

  1. __attribute__((const)) 的区别

    • __pure 函数可以读取全局变量的值,但不能修改它们。
    • __attribute__((const)) 是更严格的属性,要求函数既不能修改全局状态,也不能读取全局变量。
  2. 正确使用

    • 如果一个函数被错误地标记为 __pure,但实际上具有副作用(如修改全局变量或执行 I/O 操作),可能会导致未定义行为或错误的优化结果。
  3. 编译器支持

    • __pure 是 GCC 和 Clang 支持的扩展属性,因此在其他编译器中可能不可用。

总结

__pure 是一个用于标记纯函数的宏,通过显式声明函数的纯属性,帮助编译器进行更高效的优化。它适用于无副作用且返回值仅依赖于输入参数的函数。在使用时需要确保函数符合纯函数的定义,否则可能导致错误的优化行为。附带的链接提供了 GCC 官方文档的参考,便于开发者了解更多细节。

__printf 像 printf 一样检查格式字符串

  • format 属性用于告诉编译器某个函数的参数应该像 printf 函数那样进行格式化检查。
1
2
#define __printf(a, b)                  __attribute__((__format__(printf, a, b)))
__printf(4, 0)
  • 4 表示格式字符串是函数的第 4 个参数(从 1 开始计数,包括 this 指针或隐式参数)。
  • 0 表示该函数没有可变参数列表(即没有类似 … 的参数)。如果存在可变参数列表,则需要指定其起始位置

__cleanup 在变量的作用域结束时自动调用指定的清理函数

__cleanup 是一个 GCC 提供的属性(attribute),用于在变量的作用域结束时自动调用指定的清理函数。它是一种基于作用域的资源管理机制,能够有效减少手动管理资源释放的复杂性,避免资源泄漏。


工作原理

  1. 定义清理函数:

    • 清理函数是一个普通的 C 函数,用于释放资源或执行其他清理操作。
    • 该函数的参数通常是一个指向需要清理的变量的指针。
  2. 声明变量时使用 __cleanup:

    • 在声明变量时,通过 __cleanup 属性指定清理函数。
    • 当变量超出其作用域时,编译器会自动调用指定的清理函数。
  3. 作用域结束时自动调用:

    • 当变量的作用域结束(如函数返回或代码块结束)时,清理函数会被自动调用,无需显式调用。

声明

__fortify_function 用于启用额外的安全检查,以防止常见的编程错误,如缓冲区溢出

__LEAF 告诉编译器,该函数是一个叶函数(leaf function),即它不会调用其他函数

  • 叶函数的属性允许编译器进行进一步的优化,例如减少函数调用的开销和栈空间的使用。

__error__ 属性告诉编译器,如果该函数被调用,则会触发编译错误,并输出指定的错误消息 msg

1
2
# define __errordecl(name, msg) \
extern void name (void) __attribute__((__error__ (msg)))

__wur 表示函数的返回值不应被忽略

  • __wur__attribute__ ((__warn_unused_result__)) 的缩写,表示函数的返回值不应被忽略。这有助于防止内存泄漏,因为调用者必须处理返回的指针

__THROW && __NTH 声明函数不会抛出任何异常

__nonnull 用于指示函数的某些参数不能为空指针

  • 这有助于编译器进行静态分析,检测潜在的错误,并在编译时发出警告或错误信息,从而提高代码的安全性和可靠性。
  • 语法为 __attribute__((nonnull (arg_index1, arg_index2, ...))),其中 arg_index 是参数的索引,从 1 开始。
1
2
extern int __lxstat (int __ver, const char *__filename,
struct stat *__stat_buf) __THROW __nonnull ((2, 3));
  • __nonnull ((3)) :这表示 __fxstat 函数的第2和3个参数(即 __stat_buf)不能为空指针。如果在调用 __fxstat 函数时传递了空指针作为第三个参数,编译器会在编译时发出警告或错误信息。

__errordecl 用于声明编译时错误信息

1
2
# define __errordecl(name, msg) \
extern void name (void) __attribute__((__error__ (msg)))

__REDIRECT 将一个函数重定向到另一个函数

  • 将openalias重定向到open函数
1
2
extern int __REDIRECT (__open_alias, (const char *__path, int __oflag, ...),
open) __nonnull ((1));

关键字

__restrict 用于指示指针或引用所指向的内存区域在当前作用域内不会被其他指针或引用所别名

  • 在 C99 标准中,restrict 是标准关键字。在某些编译器实现中,可能使用 __restrict 作为扩展。
  • 通过使用 __restrict,开发人员可以告诉编译器,在当前作用域内,通过该指针访问的内存区域不会与其他指针访问的内存区域重叠。这使得编译器可以进行更激进的优化,例如更好地利用寄存器和减少内存访问,从而提高程序的性能。

__auto_type 自动推导变量的类型

__auto_type 是 GCC(GNU Compiler Collection)编译器提供的一个扩展关键字,用于自动推导变量的类型。它类似于 C++ 中的 auto 关键字,但专门用于 C 语言中。__auto_type 的主要作用是让编译器根据初始化表达式的类型自动推导变量的类型,从而简化代码书写并提高可读性。


工作原理

  1. 类型推导:

    • 使用 __auto_type 声明的变量,其类型由初始化表达式的类型决定。
    • 编译器在编译时会自动推导出变量的具体类型。
  2. 作用范围:

    • __auto_type 只能用于局部变量的声明,不能用于全局变量或函数参数。
  3. 编译器支持:

    • __auto_type 是 GCC 的扩展功能,可能不被其他编译器(如 MSVC)支持。

  1. 简化代码:

    • 减少了显式类型声明的冗余,尤其是在类型复杂的情况下。
  2. 增强可读性:

    • 代码更简洁,开发者可以专注于逻辑而非类型声明。
  3. 类型安全:

    • 在宏中使用时,__auto_type 可以确保变量的类型与初始化表达式一致,避免类型错误。
  4. 灵活性:

    • 适用于各种类型,包括基本类型、指针、结构体等。

限制

  1. 编译器依赖:

    • __auto_type 是 GCC 的扩展功能,可能不被其他编译器支持。
  2. 作用范围有限:

    • 只能用于局部变量,不能用于全局变量或函数参数。
  3. 可读性问题:

    • 对于不熟悉 __auto_type 的开发者,可能会增加理解代码的难度。

使用场景

  1. 简化类型声明:

    • 在类型复杂或不易确定的情况下,使用 __auto_type 可以减少显式类型声明的负担。
  2. 宏定义:

    • 在宏中使用 __auto_type 可以增强类型安全性,避免类型错误。
  3. 泛型操作:

    • 通过 __auto_type 实现类似泛型的功能,例如交换变量、比较大小等。

总结

__auto_type 是 GCC 提供的一个强大工具,用于在 C 语言中实现类型推导。它可以显著简化代码书写,提高代码的可读性和类型安全性,特别适合在宏定义和泛型操作中使用。然而,由于其是编译器扩展功能,在使用时需要注意兼容性问题。

全局变量

stderr 标准错误输出流

  • stderr 是标准错误输出流,通常用于输出错误和警告信息。在大多数操作系统中,stderr 是一个全局的文件流,默认情况下输出到控制台或终端。stderr 流的处理由操作系统和 C 标准库管理。通常,stderr 流是无缓冲的,这意味着每次写入操作都会立即输出到终端或控制台
  • 多线程环境中的 stderr 处理在多线程环境中,如果多个线程同时向 stderr 流写入数据,可能会导致输出混乱。

内置函数

__builtin_clz __builtin_clzll 计算一个无符号整数的前导零的数量

  • __builtin_clz 用于计算一个无符号整数(unsigned int)的前导零的数量。前导零是指从最高有效位(最左边的位)开始,连续的零的数量

  • __builtin_clzll 用于计算一个无符号长长整数(unsigned long long)的前导零的数量。它的工作原理与 __builtin_clz 类似,只是它处理的是更大的数据类型。

__builtin_constant_p 检查一个表达式是否是编译时常量

  • GCC 编译器提供的一个内建函数,用于检查一个表达式是否是编译时常量。它在编译期间进行评估,并返回一个布尔值,指示给定的表达式是否为常量.这个函数通常用于条件编译或优化代码,以便在编译时确定某些条件是否成立,从而避免在运行时进行不必要的计算。

  • 您可以使用内置函数 __builtin_constant_p 来判断表达式 exp 在编译时是否已知为常量,因此 GCC 可以对涉及该值的表达式执行常量折叠。该函数的参数是要测试的表达式。表达式不会被评估,副作用会被丢弃。如果参数是已知为编译时常量的整数 1,如果不是已知为编译时常量则返回 0。任何具有副作用的表达式都会使函数返回 0。返回 0 并不表示表达式不是常量,而只是表示 GCC 无法在活动优化选项集的约束内证明它是常量。

  • 通常您会在内存资源至关重要的嵌入式应用程序中使用此函数。如果您有一些复杂的计算,可能希望它在涉及常量时进行折叠,但不涉及常量时调用函数。

__builtin_expect 用于分支预测

  • GCC 编译器提供的一个内建函数,用于分支预测。它允许开发人员向编译器提供有关条件表达式的预期结果的信息,从而帮助编译器进行更好的优化。__builtin_expect 函数通常用于性能敏感的代码中,以提高分支预测的准确性,从而减少 CPU 的分支预测失败率,提高程序的执行效率。
  • 这个函数的语法如下:
1
long __builtin_expect (long exp, long c);
  • exp 参数是要预测的表达式,c 参数是预期的结果。这个函数返回 exp 的值,但编译器会根据 c 的值进行优化,以提高分支预测的准确性。
  • 这个函数通常用于性能敏感的代码中,以提高分支预测的准确性,从而减少 CPU 的分支预测失败率,提高程序的执行效率。它可以用于条件语句、循环和其他需要分支预测的地方。

__builtin_return_address 获取当前函数的返回地址

  • GCC 编译器提供的一个内建函数,用于获取当前函数的返回地址。它通常用于调试和错误检查,以便在运行时获取函数调用的返回地址。这对于跟踪函数调用栈和调试程序非常有用。
1
void * __builtin_return_address (unsigned int level);
  • level 参数指定要获取的返回地址的级别。level 为 0 时,返回当前函数的返回地址;level 为 1 时,返回调用当前函数的函数的返回地址;以此类推。这个函数通常用于调试和错误检查,以便在运行时获取函数调用的返回地址。这对于跟踪函数调用栈和调试程序非常有用。
  • 这个函数的返回值是一个指针,指向当前函数的返回地址。这个地址可以用于调试和错误检查,以便在运行时获取函数调用的返回地址。这对于跟踪函数调用栈和调试程序非常有用。

__builtin_unreachable 标记不可达代码

  • GCC 编译器提供的一个内建函数,用于标记不可达代码。它告诉编译器在执行到该位置时,程序将不会继续执行下去。这可以帮助编译器进行更好的优化,因为它可以假设在这个位置之后的代码永远不会被执行。使用这个函数可以提高代码的性能,并且在调试时可以帮助开发人员识别潜在的错误或不一致之处。
  • 这个函数通常用于在编译器优化时提供提示,告诉编译器某些代码永远不会被执行。这可以帮助编译器进行更好的优化,并且在调试时可以帮助开发人员识别潜在的错误或不一致之处。

__builtin_object_size 用于在编译时确定指针所指向对象的大小

  • __builtin_object_size 是 GCC 编译器提供的一个内置函数,用于在编译时确定指针所指向对象的大小。
  • 该函数接受两个参数:
    • 第一个参数是指针 ptr
    • 第二个参数是一个常量整数,用于指定计算对象大小的方式。值 0 表示返回对象的最大已知大小

__builtin_prefetch 用于在程序运行时向处理器发出预取指令

  • __builtin_prefetch 是 GCC 编译器提供的一个内置函数,用于在程序运行时向处理器发出预取指令。它允许开发人员在访问数据之前,提前将数据加载到 CPU 的缓存中,从而提高程序的性能。
  • 这个函数通常用于性能敏感的代码中,以提高数据访问的速度。它可以用于循环、数组访问和其他需要频繁访问数据的地方。使用 __builtin_prefetch 可以减少 CPU 的缓存未命中率,从而提高程序的性能。
  • 预取指令的目的是将指定的内存地址加载到缓存中,以减少后续访问该地址时的延迟。这种优化对于性能关键的代码非常有用,尤其是在处理大量数据或需要频繁访问内存的场景中
  • 需要注意的是,__builtin_prefetch 是一种低级优化工具,只有在性能瓶颈明确且经过分析验证时才应使用。不当的预取可能会导致性能下降,例如浪费缓存空间或增加内存带宽压力。此外,预取的效果依赖于硬件架构和缓存行为,因此在不同平台上的表现可能有所不同。

__builtin_prefetch 的基本语法如下:

1
__builtin_prefetch(const void *addr, int rw, int locality);
  • addr: 要预取的内存地址。通常是一个指针,指向即将被访问的数据。
  • rw: 指定预取的类型。0 表示只读预取,1 表示写预取(即数据将被修改)。
  • locality: 指定数据的局部性,范围为 0 到 3。值越高,表示数据在缓存中保留的时间越长:
    0: 数据很快会被访问一次,不需要长时间保留。
    3: 数据会被频繁访问,需要尽可能长时间保留在缓存中。

__va_arg_pack 用于处理可变参数函数

__va_arg_pack_len 用于获取可变参数的数量

函数

exit

  • 错误码参考include/linux/errno.h

perror 接受一个字符串参数,并将该字符串作为前缀输出到标准错误流

1
perror(filename);

perror 函数接受一个字符串参数,并将该字符串作为前缀输出到标准错误流(stderr),后面跟随一个冒号和一个空格,然后是与当前 errno 值对应的错误消息。

  • filename 是传递给 perror 的字符串参数,通常表示发生错误的文件的名称。
  1. 错误信息输出

    • 当文件操作失败时,例如打开文件、读取文件或写入文件失败,errno 会被设置为相应的错误代码。
    • perror(filename) 会输出类似于以下格式的错误信息:
      1
      filename: No such file or directory
    • 其中,filename 是传递给 perror 的字符串,No such file or directory 是与 errno 值对应的错误消息。

fprintf 将格式化的输出写入指定的文件流

  • 它的功能类似于 printf,但 fprintf 允许你指定输出的目标文件流,而不仅仅是标准输出(stdout)。

函数原型

1
int fprintf(FILE *stream, const char *format, ...);
  • stream:目标文件流,指定输出的目标。例如,stderr 表示标准错误输出流。
  • format:格式字符串,指定输出的格式。
  • ...:可变参数,根据格式字符串指定的格式,提供相应的值。

返回值

  • 成功时,返回写入的字符数。
  • 失败时,返回负值。

源码

  • 定义了一个增强的 fprintf 函数版本,用于在编译时进行额外的安全检查。这段代码利用了编译器的特性来提高函数的安全性和可靠性。
1
2
3
4
5
6
7
8
9
# ifdef __va_arg_pack //__va_arg_pack 是一个编译器特性,通常用于处理可变参数函数。
// __fortify_function 是一个编译器特性,用于启用额外的安全检查,以防止常见的编程错误,如缓冲区溢出
__fortify_function int
fprintf (FILE *__restrict __stream, const char *__restrict __fmt, ...)
{
return __fprintf_chk (__stream, __USE_FORTIFY_LEVEL - 1, __fmt,
__va_arg_pack ());
}
#endif

fstat 获取文件描述符 fd 对应文件的状态信息

  • fstat 函数返回 0 表示成功,返回 -1 表示失败并设置 errno

isalnum 检查字符是否为字母或数字

  1. 函数声明

    1
    int isalnum(int c);
    • isalnum 函数接受一个整数参数 c,表示要检查的字符。
    • 返回值为非零值(通常为 1)表示字符是字母或数字,返回 0 表示字符不是字母或数字。
  2. 用途

    • isalnum 函数用于判断字符是否属于字母(包括大写和小写字母)或数字(0-9)。
    • 这是一个常用的字符分类函数,在处理字符串和字符数据时非常有用。

isatty 检查文件描述符是否指向一个终端设备

参数

  • fd:一个整数,表示文件描述符。常见的文件描述符包括:
    • 0:标准输入(stdin)
    • 1:标准输出(stdout)
    • 2:标准错误输出(stderr)

返回值

  • 如果文件描述符指向一个终端设备,isatty 返回非零值(通常为 1)。
  • 如果文件描述符不指向一个终端设备,isatty 返回 0。

getopt_core.h

  • optarg 是一个指向字符的指针,用于存储当前选项的参数值
  • optind 是一个整数,表示下一个要扫描的 ARGV 元素的索引
  • opterr 是一个整数,用于控制 getopt 函数是否打印未识别选项的错误消息
  • optopt 是一个整数,用于存储未识别的选项字符

getopt_long 处理长选项(即以双破折号 -- 开头的选项)和短选项(即以单破折号 - 开头的选项)

  1. 函数声明

    1
    2
    int getopt_long(int argc, char *const argv[], const char *optstring,
    const struct option *longopts, int *longindex);
    • getopt_long 函数接受五个参数:
      • argc:命令行参数的数量。
      • argv:命令行参数的数组。
      • optstring:一个字符串,包含短选项的字符。如果一个选项需要参数,其后应跟一个冒号 :
      • longopts:一个指向 struct option 数组的指针,定义了长选项。
      • longindex:一个指向整数的指针,用于存储当前匹配的长选项在 longopts 数组中的索引。
  2. 返回值

  • getopt_long 无法匹配到命令行选项时,它会返回 ?
  • 如果遇到未知选项或选项缺少必需的参数,getopt_long 也会返回 ?
  1. struct option 结构体

    1
    2
    3
    4
    5
    6
    struct option {
    const char *name;
    int has_arg;
    int *flag;
    int val;
    };
    • struct option 结构体用于定义长选项:
      • name:长选项的名称。
      • has_arg:指示选项是否需要参数。取值可以是 no_argumentrequired_argumentoptional_argument
      • flag:如果为 NULL,则返回 val 作为选项的值;否则,将 val 存储在 flag 指向的变量中,并返回 0。
      • val:选项的值或标志。
  2. 使用示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int main(int argc, char *argv[]) {
    int opt;
    struct option longopts[] = {
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'v'},
    {"output", required_argument, NULL, 'o'},
    {0, 0, 0, 0}
    };

    while ((opt = getopt_long(argc, argv, "hvo:", longopts, NULL)) != -1) {
    switch (opt) {
    case 'h':
    printf("Usage: ...\n");
    break;
    case 'v':
    printf("Version: ...\n");
    break;
    case 'o':
    printf("Output: %s\n", optarg);
    break;
    default:
    fprintf(stderr, "Unknown option\n");
    exit(EXIT_FAILURE);
    }
    }
    return 0;
    }

getenv 获取环境变量

  • getenv 是一个标准库函数,用于获取环境变量的值。
  • getenv 函数接受一个字符串参数,表示环境变量的名称,并返回一个指向该环境变量值的指针。如果环境变量不存在,则返回 NULL
1
echo $KCONFIG_SEED # 显示环境变量的值

dummy 虚拟变量

  • dummy 是一个虚拟参数,用于确保宏展开时参数列表的长度一致。在 __count_args 宏中,dummy 被用作第一个参数,以确保即使没有传递任何参数,参数列表的长度也至少为 1。
1
2
3
4
5
6
/*
* 计算可变参数宏的参数数。目前只需要
* 它用于 1、2 或 3 个参数。
*/
#define __arg6(a1, a2, a3, a4, a5, a6, ...) a6
#define __count_args(...) __arg6(dummy, ##__VA_ARGS__, 4, 3, 2, 1, 0)

##__VA_ARGS__ 用于处理可变参数宏

  • ##__VA_ARGS__ 是一个预处理器技巧,用于处理可变参数宏。VA_ARGS 是一个特殊的宏参数,表示传递给宏的所有可变参数。## 是预处理器的连接运算符,用于将两个标识符连接在一起。
1
2
3
4
5
6
/*
* 计算可变参数宏的参数数。目前只需要
* 它用于 1、2 或 3 个参数。
*/
#define __arg6(a1, a2, a3, a4, a5, a6, ...) a6
#define __count_args(...) __arg6(dummy, ##__VA_ARGS__, 4, 3, 2, 1, 0)

_Static_assert 编译时断言

1
_Static_assert(EVT_COUNT < 256, "Can only support 256 event types with 8 bits");

语法

  • ... 是一个特殊的语法,用于表示可变参数列表。它允许函数接受不定数量的参数,通常用于实现可变参数函数(variadic functions)。可变参数函数可以接受任意数量和类型的参数,这在处理不确定数量的输入时非常有用。
  • gcc 扩展语法
1
u32 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = MPIDR_INVALID };
  1. [0 ... NR_CPUS-1] 是一个范围表达式,表示从 0 到 NR_CPUS-1 的所有索引。
  2. … 表示范围内的所有元素都将被初始化为 MPIDR_INVALID
1
2
3
4
5
6
7
8
9
10
11
Switch (i) {
case 0 ... 10:
printf("i is between 0 and 10\n");
break;
case 11 ... 20:
printf("i is between 11 and 20\n");
break;
default:
printf("i is greater than 20\n");
break;
}

_Generic 用于实现类型安全的多态函数

_Generic 简介

_Generic 是 C11 标准引入的一种关键字,用于实现类型泛型(type-generic programming)。它允许开发者根据表达式的类型选择不同的代码路径,从而实现类似于函数重载的功能。_Generic 提供了一种在编译时根据类型分发逻辑的机制,而无需运行时判断。


使用场景

  1. 类型安全的多态函数

    • 在 C 中,函数重载并不被直接支持。通过 _Generic,可以根据参数的类型选择不同的实现,从而实现类似于 C++ 中函数重载的功能。
    • 例如:为不同类型的输入调用不同的处理函数。
  2. 简化代码逻辑

    • 在需要对不同类型执行不同操作的场景中,_Generic 可以避免冗长的 if-elseswitch 语句。
  3. 提高代码可读性和可维护性

    • 通过 _Generic,可以将类型分发逻辑集中到一个地方,减少重复代码。
  4. 内核开发和底层优化

    • 在 Linux 内核或其他底层代码中,_Generic 常用于为不同类型的同步原语(如自旋锁、互斥锁等)提供统一的接口。

工作原理

_Generic 的基本语法如下:

1
_Generic(expression, type1: result1, type2: result2, ..., default: default_result)
  • expression:需要判断类型的表达式。
  • type1, type2, ...:列出可能的类型。
  • result1, result2, ...:对应类型的返回值或代码路径。
  • default(可选):当 expression 的类型不匹配任何列出的类型时,选择的默认结果。

实现细节

  1. 编译时类型检查

    • _Generic 在编译时解析表达式的类型,并选择匹配的分支。这种机制避免了运行时的类型判断,提高了性能。
  2. 类型匹配规则

    • _Generic 使用严格的类型匹配规则。例如,intunsigned int 是不同的类型,必须分别列出。
    • 如果没有匹配的类型且未提供 default 分支,编译器会报错。
  3. 与宏结合

    • _Generic 通常与宏结合使用,以实现更灵活的接口。例如,可以为不同类型的输入调用不同的函数。
  4. 嵌套使用

    • _Generic 可以嵌套使用,以处理更复杂的类型分发逻辑。

优缺点

优点

  1. 类型安全
    • _Generic 在编译时进行类型检查,避免了运行时错误。
  2. 性能高效
    • 类型分发逻辑在编译时完成,不会增加运行时开销。
  3. 代码简洁
    • 避免了冗长的 if-elseswitch 语句,提高了代码的可读性。

缺点

  1. 类型匹配限制
    • _Generic 需要显式列出所有可能的类型,无法处理动态类型。
  2. 复杂性
    • 对于复杂的类型分发逻辑,代码可能变得难以维护。
  3. 兼容性
    • _Generic 是 C11 标准的一部分,不支持 C11 的编译器无法使用。

总结

_Generic 是 C11 提供的一种强大工具,用于实现类型安全的多态函数和类型分发逻辑。它在编译时根据表达式的类型选择代码路径,既提高了性能,又增强了代码的可读性和安全性。尽管存在一些限制,但在需要处理多种类型的场景中,_Generic 是一种非常实用的解决方案,尤其是在底层开发和性能优化中。