gcc__attribute__((cleanup))解析

原创 Linux二进制 2024-11-06 08:20

引言

在 C/C++ 语言编程中,资源管理是一个十分重要的内容。确保资源在不再需要时被正确释放,可以避免内存泄漏和其他资源管理问题。GCC 提供了一个强大的扩展属性 __attribute__((cleanup(cleanup_function))),用于在变量作用域结束时自动调用一个清理函数。本文将详细介绍这一特性的原理、实际案例以及如何在项目中应用它。

工作原理

__attribute__((cleanup(cleanup_function))) 是 GCC 提供的一个扩展属性,用于在变量作用域结束时自动调用一个指定的清理函数。这个特性特别适用于需要确保资源在变量生命周期结束时被正确释放的场景,例如关闭文件描述符、释放内存等。

当变量的作用域结束时(例如函数返回、块结束或异常抛出),GCC 会自动调用指定的清理函数。清理函数接收一个指向变量的指针,因此可以修改变量的值或执行其他清理操作。

__attribute__((cleanup((cleanup_function))) 语法如下:

void cleanup_function(type *var);
type var __attribute__((cleanup(cleanup_function)));
  • type: 变量的类型。

  • var: 需要应用清理属性的变量。

  • cleanup_function: 清理函数,接受一个指向变量类型的指针作为参数。

代码演示

1. 释放动态分配的内存

常见的用例是确保动态分配的内存被正确释放,让我们通过代码演示一下。首先创建一个未释放申请的动态内存的 ufree_memory.c 文件,再创建一个通过 cleanup 函数释放了申请的动态内存的 free_memory.c 文件,并使用 valgrind 检测一下两个文件内存泄露情况。

源文件 ufree_memory.c

#include 
#include

int main() {
char *str = malloc(100 * sizeof(char));
if (str == NULL) {
perror("malloc");
return 1;
}

// 使用分配的内存
snprintf(str, 100, "Hello, World!");
printf("%s\n", str);

// 内存将在 main 函数结束时自动释放
return 0;
}

编译上面源文件,生成可执行文件并通过 valgrind 运行如下:

[root@localhost gcc_property]# gcc -g -o ufree_memory ufree_memory.c
[root@localhost gcc_property]# ls
ufree_memory ufree_memory.c

[root@localhost gcc_property]# valgrind --tool=memcheck --leak-check=full ./ufree_memory
==1430643== Memcheck, a memory error detector
==1430643== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1430643== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==1430643== Command: ./ufree_memory
==1430643==
Hello, World!
==1430643==
==1430643== HEAP SUMMARY:
==1430643== in use at exit: 100 bytes in 1 blocks
==1430643== total heap usage: 2 allocs, 1 frees, 1,124 bytes allocated
==1430643==
==1430643== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1430643== at 0x4C392A1: malloc (vg_replace_malloc.c:446)
==1430643== by 0x4006FB: main (ufree_memory.c:13)
==1430643==
==1430643== LEAK SUMMARY:
==1430643== definitely lost: 100 bytes in 1 blocks
==1430643== indirectly lost: 0 bytes in 0 blocks
==1430643== possibly lost: 0 bytes in 0 blocks
==1430643== still reachable: 0 bytes in 0 blocks
==1430643== suppressed: 0 bytes in 0 blocks
==1430643==
==1430643== For lists of detected and suppressed errors, rerun with: -s
==1430643== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

通过 Valgrind 的输出,我们可以得出以下结论:

  • 内存泄漏: 100 bytes in 1 blocks are definitely lost:程序中确实存在内存泄漏,100 字节的内存块在程序结束时没有被释放。 definitely lost: 100 bytes in 1 blocksValgrind 确定这 100 字节的内存是完全丢失的,即没有被释放。

  • 内存分配和释放: total heap usage2 allocs, 1 frees, 1,124 bytes allocated:程序总共进行了 2 次内存分配,1 次内存释放,总共分配了 1,124 字节的内存。 in use at exit100 bytes in 1 blocks:在程序退出时,仍有 100 字节的内存未被释放。

  • 泄漏位置: at 0x4C392A1: malloc (vg_replace_malloc.c:446):内存泄漏发生在 malloc 调用处。 by 0x4006FB: main (ufree_memory.c:13):具体的泄漏位置是在 main 函数的第 13 行,即 char *str = malloc(100 * sizeof(char)); 这一行。

根据上述结论,我们可以确定问题出在 main 函数的第 13 行,即 char *str = malloc(100 * sizeof(char)); 分配的内存没有被释放。为了修复这个问题,可以使用 __attribute__((cleanup(cleanup_function))) 属性来确保内存被自动释放。

 

拓展Valgrind 的输出,为什么会有 2 次内存分配和 1 次内存释放?

  • 2 次内存分配:第 1 次内存分配:在 main 函数中,char *str = malloc(100 * sizeof(char)); 这一行分配了 100 字节的内存。第 2 次内存分配:Valgrind 本身可能在内部进行了一些额外的内存分配。这些分配通常是为了跟踪内存使用情况和检测内存泄漏。这些内部分配不会影响你的程序逻辑,但会出现在 Valgrind 的输出中。

  • 1 次内存释放:唯一的 1 次内存释放:Valgrind 内部分配的内存在程序结束时被释放。在你的源代码中,没有任何地方显式地释放 str 指向的 100 字节内存。因此,这 100 字节的内存没有被释放,导致内存泄漏。

源文件 free_memory.c :

#include 
#include

// 清理函数,释放内存
void free_memory(char **ptr) {
if (*ptr != NULL) {
free(*ptr);
printf("Memory freed\n");
}
}

int main() {
char *str __attribute__((cleanup(free_memory))) = malloc(100 * sizeof(char));
if (str == NULL) {
perror("malloc");
return 1;
}

// 使用分配的内存
snprintf(str, 100, "Hello, World!");
printf("%s\n", str);

// 内存将在 main 函数结束时自动释放
return 0;
}

在这个例子中,free_memory 函数会在 str 变量的作用域结束时自动调用,确保分配的内存被释放。重新编译并运行 Valgrind 检测:

[root@localhost gcc_property]# gcc -g -o free_memory free_memory.c
[root@localhost gcc_property]# ls
free_memory free_memory.c ufree_memory ufree_memory.c

[root@localhost gcc_property]# valgrind --tool=memcheck --leak-check=full ./free_memory
==1431439== Memcheck, a memory error detector
==1431439== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1431439== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==1431439== Command: ./free_memory
==1431439==
Hello, World!
Memory freed
==1431439==
==1431439== HEAP SUMMARY:
==1431439== in use at exit: 0 bytes in 0 blocks
==1431439== total heap usage: 2 allocs, 2 frees, 1,124 bytes allocated
==1431439==
==1431439== All heap blocks were freed -- no leaks are possible
==1431439==
==1431439== For lists of detected and suppressed errors, rerun with: -s
==1431439== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

通过 Valgrind 的输出,我们可以得出以下结论:

  1. 程序输出: Hello, World!:程序成功打印了字符串 "Hello, World!"。 Memory freed:程序在 main 函数结束时调用了 free_memory 函数,释放了分配的内存,并打印了 "Memory freed"

  2. HEAP SUMMARY: in use at exit: 0 bytes in 0 blocks:程序结束时,所有分配的内存都已被释放,没有内存泄漏。 total heap usage: 2 allocs, 2 frees, 1,124 bytes allocated: 2 allocs:程序进行了两次内存分配。一次是你显式调用的 malloc(100 * sizeof(char)),另一次可能是 Valgrind 内部分配的内存。 2 frees:程序进行了两次内存释放。一次是你显式调用的 free_memory 函数释放的内存,另一次可能是 Valgrind 内部分配的内存被释放。 1,124 bytes allocated:总共分配了 1,124 字节的内存,其中 100 字节是你显式分配的,其余 1,024 字节可能是 Valgrind 内部分配的。

  3. All heap blocks were freed -- no leaks are possible:这表明所有分配的内存块都已成功释放,没有内存泄漏。

  4. ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)Valgrind 没有检测到任何错误,包括内存泄漏、越界访问等。

至此,程序按预期运行,没有检测到任何错误。我们可以确认内存泄漏问题已经得到解决。

2. 关闭文件描述符

假设我们有一个函数,需要在函数退出时确保文件描述符被关闭。可以使用 __attribute__((cleanup(cleanup_function))) 来实现这一点。

源文件 close_fd.c

#include 
#include
#include

// 清理函数,关闭文件描述符
void close_fd(int *fd) {
if (*fd >= 0) {
close(*fd);
printf("File descriptor %d closed\n", *fd);
}
}

int main() {
int fd __attribute__((cleanup(close_fd))) = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}

// 读取文件内容
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
if (bytes_read < 0) {
perror("read");
return 1;
}
buffer[bytes_read] = '\0';
printf("Read: %s\n", buffer);

// 文件描述符将在 main 函数结束时自动关闭
return 0;
}

编译上面源文件,生成并运行可执行文件,

[root@localhost gcc_property]# gcc -g -o close_fd close_fd.c
[root@localhost gcc_property]# cat example.txt
This is a demo to demonstration the usage of __attribute__((cleanup(cleanup_function)))
[root@localhost gcc_property]# ./close_fd
Read: This is a demo to demonstration the usage of __attribute__((cleanup(cleanup_function)))
File descriptor 3 closed

在这个例子中,close_fd 函数会在 fd 变量的作用域结束时自动调用,确保文件描述符被关闭。

通过打印内容,我们可以知道文件描述符已经成功关闭。但是即使程序输出了 File descriptor 3 closed,也有可能存在某些特殊情况或错误导致文件描述符没有真正关闭。如果我们想进一步确认文件描述符确实被关闭,我们也可以通过 Valgrind ,其不仅可以检测内存泄漏,还可以检测文件描述符等资源的泄漏。可以使用 Valgrind 的 --track-fds=yes 选项来跟踪文件描述符的使用情况。

Valgrind 会输出文件描述符的打开和关闭情况。如果文件描述符被正确关闭,你不会看到任何未关闭的文件描述符的警告。使用 Valgrind 检测输出如下:

[root@localhost gcc_property]# valgrind --tool=memcheck --leak-check=full --track-fds=yes ./close_fd
==1436843== Memcheck, a memory error detector
==1436843== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==1436843== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==1436843== Command: ./close_fd
==1436843==
Read: This is a demo to demonstration the usage of __attribute__((cleanup(cleanup_function)))
File descriptor 3 closed
==1436843==
==1436843== FILE DESCRIPTORS: 3 open (3 std) at exit.
==1436843==
==1436843== HEAP SUMMARY:
==1436843== in use at exit: 0 bytes in 0 blocks
==1436843== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==1436843==
==1436843== All heap blocks were freed -- no leaks are possible
==1436843==
==1436843== For lists of detected and suppressed errors, rerun with: -s
==1436843== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

这段 Valgrind 的输出解析如下:

  1. 程序输出: ReadThis is a demo to demonstration the usage of attribute((cleanup(cleanup_function))):程序成功读取并打印了文件内容。 File descriptor 3 closed:程序在 main 函数结束时调用了 close_fd 函数,关闭了文件描述符 3,并打印了关闭信息。

  2. 文件描述符跟踪: FILE DESCRIPTORS: 3 open (3 std) at exit.:程序结束时,有 3 个文件描述符仍然打开,但这 3 个文件描述符都是标准输入(0)、标准输出(1)和标准错误(2)。这意味着除了这三个标准文件描述符外,没有其他文件描述符被打开而未关闭。

  3. HEAP SUMMARY: in use at exit: 0 bytes in 0 blocks:程序结束时,所有分配的内存都已被释放,没有内存泄漏。 total heap usage1 allocs, 1 frees, 1,024 bytes allocated: 1 allocs:程序进行了 1 次内存分配。 1 frees:程序进行了 1 次内存释放。 1,024 bytes allocated:总共分配了 1,024 字节的内存。

  4. 内存泄漏检查: All heap blocks were freed -- no leaks are possible:所有分配的内存块都已成功释放,没有内存泄漏。

  5. 错误总结: ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)Valgrind 没有检测到任何错误,包括内存泄漏、越界访问等。

通过这段 Valgrind 输出,我们可以得出以下结论:

  • 文件描述符管理正确:程序正确地打开了文件描述符 3 并在 main 函数结束时关闭了它。除了标准输入、标准输出和标准错误外,没有其他文件描述符被打开而未关闭。

  • 内存管理正确:程序正确地分配和释放了内存,没有内存泄漏。

  • 程序运行正常:程序按预期运行,没有检测到任何错误。

总结

__attribute__((cleanup(cleanup_function)))是 GCC 提供的一个强大工具,用于在变量作用域结束时自动调用清理函数。通过使用这一特性,可以编写更简洁、更安全的代码,确保资源在不再需要时被正确释放。无论是释放内存,关闭文件描述符、还是其他资源管理任务,__attribute__((cleanup_function)) 都能提供有效的解决方案。希望本文能帮助你在项目中更好地利用这一特性,提高代码的质量和可靠性。


Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
评论
  • 光耦合器作为关键技术组件,在确保安全性、可靠性和效率方面发挥着不可或缺的作用。无论是混合动力和电动汽车(HEV),还是军事和航空航天系统,它们都以卓越的性能支持高要求的应用环境,成为现代复杂系统中的隐形功臣。在迈向更环保技术和先进系统的过程中,光耦合器的重要性愈加凸显。1.混合动力和电动汽车中的光耦合器电池管理:保护动力源在电动汽车中,电池管理系统(BMS)是最佳充电、放电和性能监控背后的大脑。光耦合器在这里充当守门人,将高压电池组与敏感的低压电路隔离开来。这不仅可以防止潜在的损坏,还可以提高乘
    腾恩科技-彭工 2024-11-29 16:12 118浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 75浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 161浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 157浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 88浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 57浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 65浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 63浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 60浏览
  • 在电子技术快速发展的今天,KLV15002光耦固态继电器以高性能和强可靠性完美解决行业需求。该光继电器旨在提供无与伦比的电气隔离和无缝切换,是现代系统的终极选择。无论是在电信、工业自动化还是测试环境中,KLV15002光耦合器固态继电器都完美融合了效率和耐用性,可满足当今苛刻的应用需求。为什么选择KLV15002光耦合器固态继电器?不妥协的电压隔离从本质上讲,KLV15002优先考虑安全性。输入到输出隔离达到3750Vrms(后缀为V的型号为5000Vrms),确保即使在高压情况下,敏感的低功耗
    克里雅半导体科技 2024-11-29 16:15 119浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦