C语言中attribute关键字的用法与作用是什么?

99ANYc3cd6
预计阅读时长 22 分钟
位置: 首页 C语言 正文

什么是 __attribute__

__attribute__ 是 GCC(GNU Compiler Collection)和 Clang 编译器提供的一个强大的扩展功能,它允许开发者向编译器提供额外的信息,从而影响代码的编译过程、优化方式或运行时行为。

它是一个“编译器提示”或“编译器指令”,让你能够告诉编译器一些标准 C 语言语法无法表达的事情。


基本语法

__attribute__ 的基本语法如下:

__attribute__((attribute-list))

它可以放在以下位置:

  1. 函数声明/定义后:用于修饰函数。
  2. 变量声明/定义后:用于修饰变量。
  3. 结构体/联合体成员后:用于修饰成员。
  4. 类型定义后:用于修饰 typedef

attribute-list 是一个由逗号分隔的属性列表,((noreturn, warn_unused_result))

注意

  • __attribute__ 是两个下划线开头和结尾,这是 GCC 的约定。
  • 由于它是编译器扩展,不是标准 C 的一部分,使用它可能会降低代码的可移植性,但它在 Linux 内核、嵌入式系统等高性能领域被广泛使用。

常用 __attribute__ 属性详解

下面我们介绍一些最常用和最重要的 __attribute__ 属性,并附上示例。

noreturn

作用:告诉编译器,这个函数永远不会返回,这意味着函数内部要么会无限循环,要么会通过 exit(), abort() 等方式终止程序,执行流程不会“返回”到调用点。

优点

  • 优化:编译器可以省去为函数返回创建栈帧等操作,进行优化。
  • 消除警告:可以消除 warning: 'noreturn' function does return 这样的警告。
  • 静态分析:帮助静态分析工具(如 clang-tidy)理解代码流,在 noreturn 函数调用之后的代码,会被判定为“不可达代码”,从而可能产生新的警告。

示例

#include <stdlib.h>
// 告诉编译器,这个函数永远不会返回
void my_exit(void) __attribute__((noreturn));
void my_exit(void) {
    printf("Exiting program...\n");
    exit(1); // 调用 exit,确实不会返回
}
int main(void) {
    int a = 10;
    if (a > 0) {
        my_exit(); // 调用 my_exit
    }
    // 因为 my_exit 是 noreturn,编译器知道这行代码永远不会执行
    printf("This line will never be printed.\n"); 
    return 0;
}

aligned(n)

作用:指定变量或结构体的对齐方式为 n 字节,对齐可以提高内存访问效率,避免性能下降,甚至在某些架构上是必须的。

示例

// 强制变量 v 的内存地址必须是 16 的倍数
int var __attribute__((aligned(16)));
// 强制结构体 S 的内存对齐为 32 字节
struct S {
    char c;
    int i;
} __attribute__((aligned(32)));
int main(void) {
    printf("Address of var: %p\n", (void*)&var);
    // 输出地址的最低 4 位应该是 0,表示 16 字节对齐
    struct S s;
    printf("Address of s: %p\n", (void*)&s);
    return 0;
}

packed

作用:告诉编译器不要在结构体成员之间或结构体末尾插入任何填充字节(padding),这可以最小化结构体的大小,但可能会以牺牲内存访问效率为代价。

用途

  • 与硬件寄存器交互,硬件要求特定的数据布局。
  • 网络协议解析,数据包需要精确的字节对齐。

示例

// 没有使用 packed,编译器可能会在 char 和 int 之间填充 3 个字节
struct normal_struct {
    char c;
    int i;
};
// sizeof(struct normal_struct) 通常是 8 (1 + 3 padding + 4)
// 使用 packed,编译器不会插入任何填充
struct packed_struct {
    char c;
    int i;
} __attribute__((packed));
// sizeof(struct packed_struct) 是 5 (1 + 4)

deprecatedunavailable

作用:标记一个函数或变量为“已弃用”或“不可用”,当开发者尝试使用它们时,编译器会产生警告或错误。

deprecated:产生警告,允许代码继续编译,但强烈建议不要使用。 unavailable:产生错误,阻止代码编译。

示例

// 产生警告
void old_function(void) __attribute__((deprecated));
// 产生编译错误
void future_removed_function(void) __attribute__((unavailable));
void old_function(void) {
    printf("This is an old function.\n");
}
int main(void) {
    old_function(); // 编译时会产生警告
    // future_removed_function(); // 这行会导致编译错误
    return 0;
}

format (arch, arg_index)

作用:用于检查格式化字符串函数(如 printf, scanf)的参数类型是否正确,这是防止格式化字符串漏洞(Format String Vulnerability)的利器。

  • arch: printfscanf
  • arg_index: 格式化字符串参数的位置(从 1 开始)。

示例

#include <stdio.h>
// 告诉编译器,检查第一个参数(格式化字符串)和第三个参数(...中的第一个)
void my_printf(const char *fmt, ...) __attribute__((format(printf, 1, 3)));
void my_printf(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}
int main(void) {
    int num = 10;
    // 正确用法,编译正常
    my_printf("The number is %d\n", num); 
    // 错误用法,传错了类型,编译器会警告
    my_printf("The number is %s\n", num); 
    // 警告信息类似:format '%s' expects argument of type 'char *', but argument 3 has type 'int'
    return 0;
}

const

作用:告诉编译器,这个函数除了返回值外,不会修改任何全局或静态变量,也不会有任何副作用(如 I/O 操作)。

优点

  • 强大的优化:编译器可以安全地缓存函数的返回结果,如果函数被多次调用,且参数相同,编译器可能会只计算一次并重用结果,甚至可能完全内联该函数。

示例

// 告诉编译器,这个函数是纯函数,没有副作用
int square(int x) __attribute__((const));
int square(int x) {
    return x * x;
}
int main(void) {
    int a = 5;
    // 编译器可能会优化成 a = 25;
    a = square(5); 
    // 在一个循环中,这种优化效果更明显
    long sum = 0;
    for (int i = 0; i < 100; i++) {
        sum += square(i); // 编译器可能会将 square(i) 的结果缓存
    }
    return 0;
}

section("section_name")

作用:将变量或函数放置在指定的 ELF 文件段中,这对于嵌入式开发至关重要,将初始化数据放在 .init_array 段,或将特定数据放在特定的内存区域。

示例

// 将变量 my_data 放在名为 "my_data_section" 的自定义段中
int my_data __attribute__((section("my_data_section"))) = 123;
// 将函数 my_func 放在名为 "my_code_section" 的自定义段中
void my_func(void) __attribute__((section("my_code_section"))) {
    // ...
}
int main(void) {
    my_func();
    return 0;
}

编译后,你可以使用 readelf -S your_program 查看段信息,会看到 my_data_sectionmy_code_section

weak

作用:将一个符号(函数或变量)标记为“弱符号”。

  • 弱函数:如果另一个文件中有一个同名的“强符号”(普通定义),则使用强符号,如果没有,则使用这个弱符号。
  • 弱变量:与函数类似,如果强符号存在,则覆盖弱符号。

用途

  • 提供默认实现,允许用户覆盖,库可以提供一个默认的 log 函数,用户可以提供自己的 log 函数来替代它。
  • 在链接阶段解析未定义符号,避免链接错误。

示例文件 a.c (提供弱符号)

// 提供一个默认的、可被覆盖的 log 函数
void __attribute__((weak)) log_message(const char *msg) {
    printf("Default log: %s\n", msg);
}

文件 main.c (使用并可能覆盖)

// extern 声明 log_message,此时它是一个弱符号
extern void log_message(const char *msg);
// main.c 也定义了一个同名的 log_message,它就是强符号,会覆盖 a.c 中的
void log_message(const char *msg) {
    printf("Custom log: [%s]\n", msg);
}
int main(void) {
    log_message("Hello, world!");
    return 0;
}

编译链接 a.cmain.c,最终会调用 main.c 中定义的 log_message,如果只编译 a.c,则调用默认的。


如何检查属性是否生效?

你可以使用 -S 标志让 GCC 生成汇编代码,然后检查汇编输出,看看编译器是否根据你的 __attribute__ 做了相应的处理。

对于 noreturn

gcc -S -O2 noreturn_example.c

然后打开 noreturn_example.s 文件,你会发现 my_exit 函数的结尾可能直接是 jmp exit 或类似的指令,而没有 ret (return) 指令。

对于 aligned

gcc -S aligned_example.c

在汇编代码中,你可能会看到 .p2align 4 这样的指令,用于确保变量 var 的地址对齐到 16 字节。


__attribute__ 是 GCC/Clang 提供的一个非常强大的工具,它极大地增强了 C 语言的表达能力,虽然它不是标准 C,但在特定领域(尤其是需要与底层硬件或进行深度优化的场景)中,它几乎是不可或缺的。

核心要点

  1. 它是编译器扩展,用于向编译器提供额外信息。
  2. 语法__attribute__((...))
  3. 常用属性noreturn, aligned, packed, deprecated, format, const, section, weak 等。
  4. 优点:优化代码、增强安全性、实现特殊功能(如自定义内存布局)。
  5. 可移植性:使用时需考虑,但可以通过宏进行条件编译来兼容其他编译器(如 #ifdef __GNUC__)。
-- 展开阅读全文 --
头像
dede如何调用文章浏览次数?
« 上一篇 今天
dede 后台文章列表空白
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]