C语言资源管理实践-DEFER

🏛️ best365官网登陆 ⏳ 2025-07-19 01:29:44 👤 admin 👁️ 5505 💎 912
C语言资源管理实践-DEFER

笔者近来和朋友谈论了有关C语言的资源管理方式,在ISO C中,资源管理一直都以很原始的方式进行——函数库提供open和close接口,由程序员亲自管理销毁资源的时机,更进一步无非是使用goto这样原始的关键字来去重。

GNU C 资源管理在GNU Extensions的加持下,我们有cleanup属性,可以为变量指定一个在离开作用域时自动执行的函数:

#include

void

fcleanup(FILE** fp)

{

printf("cleaning\n");

if (*fp) {

fclose(*fp);

}

}

int

main()

{

FILE* fp __attribute__((cleanup(fcleanup))) = fopen("file.txt", "w");

}fp离开作用域时,也就是主函数结束时,会自动运行fcleanup,如果使用-std=gnu23,那么也可以这么书写属性:

FILE* fp [[gnu::cleanup(fcleanup)]] = fopen("file.txt", "w");这个方案适用于clang和gcc,但缺点是我们需要为每个类型重写一个释放函数。

因为如果要释放FILE*类型的资源,我们需要用以FILE**为参数的函数

DEFER写过zig等语言的读者可能会了解defer这个关键字,简单来说,defer定义一段表达式,它会在离开当前作用域时执行:

const std = @import("std");

const print = std.debug.print;

pub fn main() !void {

defer print("exec third\n", .{});

if (false) {

defer print("will not exec\n", .{});

}

defer {

print("exec second\n", .{});

}

defer {

print("exec first\n", .{});

}

}zig用这种语法将资源申请和释放写在一起,这样既保证了不会遗漏释放流程,也确保了释放流程的可自定义性,我们希望C语言也能有类似的功能,该如何实现呢?

GNU Nested Function实际上,GNU C允许用户定义嵌套函数:

int

main() {

int foo() {

return 0;

}

return foo();

}这种特性只有gcc支持,clang不支持,甚至g++也不支持。

我们可以利用这个特性来封装一块代码块:

int

main()

{

void fcleanup(int**) {

// code block here

}

int* fcleanup_placeholder __attribute__((cleanup(fcleanup), unused)) = NULL;

}这样,我们写在函数中的代码就可以做出类似defer一样的行为,我们将该功能封装成宏:

// 用来生成不重复ID的工具宏

#define DEFER_CONCAT(a, b) DEFER_CONCAT_INNER(a, b)

#define DEFER_CONCAT_INNER(a, b) a##b

#define DEFER_UNIQUE_NAME(base) DEFER_CONCAT(base, __COUNTER__)

// DEFER 实现

#define DEFER_IMPL(fname, phname, ...) \

void fname(int**) \

{ \

__VA_ARGS__ \

} \

int* phname __attribute__((cleanup(fname), unused)) = NULL

#define DEFER(...) \

DEFER_IMPL(DEFER_UNIQUE_NAME(defer_block), \

DEFER_UNIQUE_NAME(defer_block_ph), \

__VA_ARGS__)测试一下:

int

main()

{

DEFER(printf("on exit\n"););

printf("in function body\n");

}输出:

in function body

on exitBlocks说完了gcc,我们来看clang,对于clang来说,没有嵌套函数这样方便的功能,但它有名为blocks的特性,通过-fblocks开启,它定义了一种新的函数指针,可以将代码块封装为函数:

int

main()

{

void(^fblock)(void) = ^{

printf("hello in block");

};

}这样的一个代码块可以作为函数被调用,那么,我们可以直接为它定义一个cleanup属性,让其在cleanup时调用自己:

void

defer_block_cleanup(void (^*block)(void))

{

if (*block) {

(*block)();

}

}

int

main()

{

void(^fblock)(void) __attribute__((cleanup(defer_block_cleanup), unused)) = ^{

printf("hello in block");

};

}同样将其封装为宏:

void

defer_block_cleanup(void (^*block)(void))

{

if (*block) {

(*block)();

}

}

#define DEFER(...) \

void (^DEFER_UNIQUE_NAME(defer_block))(void) \

__attribute__((cleanup(defer_block_cleanup), unused)) = (^{ \

__VA_ARGS__ })

#define DEFER_IF(cond, ...) DEFER(if (cond){ __VA_ARGS__ })能够做到和上面相同的效果。

这里也解释了为什么gcc的实现使用空指针,因为clang的实现必须占用一个函数指针,为了宏的效果相同,所以gcc版本使用指针占位符

写在后面本文代码仓库:cdefer

说实在的,这种实现高度依赖编译器特性,不应看作一种行之有效的解决方案,但无奈标准直到C23都没有进一步优化资源管理手段,文中提到的blocks、cleanup和defer等特性也是遥遥无期。

作为C语言爱好者,笔者真切希望标准能给出官方的新资源管理方案。

相关掠夺

beat365网页登录
振动模拟器 - 在线振动您的手机

振动模拟器 - 在线振动您的手机

🗓️ 07-15 👁️ 8690