C语言实现Go的defer功能
之前笔者写了一篇博文 C++实现Go的defer功能,介绍了如何在C++语言中实现Go的defer功能,那在C语言中是否也可以实现这样的功能呢?本文就将介绍一下如何在C语言中实现Go的defer功能。
我们还是使用 C++实现Go的defer功能中的示例:
1void test()
2{
3 FILE* fp = fopen("test.txt", "r");
4 if (nullptr == fp)
5 return;
6
7 defer(fclose(fp));
8 if (...)
9 {
10 return;
11 }
12 if (...)
13 {
14 return;
15 }
16 if (...)
17 {
18 return;
19 }
20}
为了实现该功能,需要借助编译器的扩展功能,GCC/Clang的cleanup
属性,微软目前的编译器不支持该扩展属性,所以本文介绍的方法不适用于微软编译器。
一、GCC编译器
GCC的cleanup
属性,可以参见GCC官方
文档var-attr-cleanup-cleanup_function或者
文档index-cleanup-variable-attribute。
GCC下的cleanup
属性用法:
1static void foo(int *p) { printf("foo called\n"); }
2static void bar(int *p) { printf("bar called\n"); }
3void baz(void)
4{
5 int x __attribute__((cleanup(foo)));
6 { int y __attribute__((cleanup(bar))); }
7}
GCC支持函数嵌套,cleanup
属性也支持不带参数的函数调用(注意:C23以前是支持不带参数,C23还是必须要有一个参数):
1void test()
2{
3 void ff() { printf("ff called\n"); }
4 int z __attribute__((cleanup(ff)));
5}
所以要实现前面示例中的写法也很简单,使用宏实现一个嵌套函数来执行自定义的表达式,再定义一个变量带上cleanup
属性即可:
1#define __CONCAT0__(a, b) a##b
2#define __CONCAT__(a, b) __CONCAT0__(a, b)
3#define __DEFER__(exp, COUNTER) \
4 void __CONCAT__(_DEFER_FUNC_, COUNTER)() { exp; } \
5 int __CONCAT__(_DEFER_VAR_, COUNTER) \
6 __attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
7#define defer(exp) __DEFER__(exp, __COUNTER__)
二、Clang编译器
Clang的cleanup
属性,可以参见Clang官方
文档。由于Clang目前不支持函数嵌套,但提供了一种叫做Block
类型的语法扩展,参见
文档,有点类似C++的Lambda 表达式。
Clang的cleanup
属性调用的函数,必须带有一个参数,否则会报错:
1error: 'cleanup' function 'ff' must take 1 parameter
Clang需要写成:
1void ff(int* p) { printf("ff called\n"); }
2void test()
3{
4 int z __attribute__((cleanup(ff)));
5}
但是前面示例的写法是:
1defer(fclose(fp));
函数内部的表达式无法写到外部去,所以就需要借助Clang的Block
类型语法扩展了:
1//需要先定义一个外部函数,且必须带一个参数,这个参数的类型是一个`Block`类型
2static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
3void test() {
4 // 定义一个ff变量,该变量是一个`Block`类型
5 void (^ff)() = ^{
6 printf("ff called\n");
7 };
8 // 可以像函数一样使用,直接调用
9 ff();
10 // 定义一个`Block`类型的x变量,它的值一个是表达式,且拥有`cleanup`属性,调用外部函数__cleanup__
11 __attribute__((cleanup(__cleanup__))) void (^x)() = ^{
12 printf("x called\n");
13 };
14}
编译时必须使用-fblocks
参数进行编译,同时链接BlocksRuntime
库。Ubuntu下可以使用下面的命令安装BlocksRuntime
库:
1sudo apt install libblocksruntime-dev
如果不安装也可以自己定义一下变量:
1void *_NSConcreteGlobalBlock[32] = {0};
2void *_NSConcreteStackBlock[32] = {0};
注意:MinGW下目前没有可用的BlocksRuntime
库,在链接时会报错:
1undefined reference to `__imp__NSConcreteGlobalBlock'
2undefined reference to `__imp__NSConcreteStackBlock'
这就必须自己定义一下变量了:
1void *__imp__NSConcreteGlobalBlock[32] = {0};
2void *__imp__NSConcreteStackBlock[32] = {0};
三、跨GCC/Clang编译器
综上,为了让Clang与GCC都能支持前面示例中的写法,可以使用宏来分别处理:
1#define __CONCAT0__(a, b) a##b
2#define __CONCAT__(a, b) __CONCAT0__(a, b)
3
4#if defined(__clang__)
5#if defined(__MINGW32__) || defined(__MINGW64__)
6void *__imp__NSConcreteGlobalBlock[32] = {0};
7void *__imp__NSConcreteStackBlock[32] = {0};
8#elif defined(__LINUX__)
9void *_NSConcreteGlobalBlock[32] = {0};
10void *_NSConcreteStackBlock[32] = {0};
11#endif
12static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
13#define defer(exp) \
14 __attribute__((cleanup(__cleanup__))) void ( \
15 ^__CONCAT__(_DEFER_VAR_, __COUNTER__))(void) = ^{ \
16 exp; \
17 }
18#elif defined(__GNUC__)
19#define __DEFER__(exp, COUNTER) \
20 void __CONCAT__(_DEFER_FUNC_, COUNTER)(int *p) { exp; } \
21 int __CONCAT__(_DEFER_VAR_, COUNTER) \
22 __attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
23#define defer(exp) __DEFER__(exp, __COUNTER__)
24#else
25#error "compiler not support!"
26#endif
下面给出完整代码:
main.c:
1#include <stdio.h>
2#include <stdlib.h>
3
4#define __CONCAT0__(a, b) a##b
5#define __CONCAT__(a, b) __CONCAT0__(a, b)
6
7#if defined(__clang__)
8#if defined(__MINGW32__) || defined(__MINGW64__)
9void *__imp__NSConcreteGlobalBlock[32] = {0};
10void *__imp__NSConcreteStackBlock[32] = {0};
11#elif defined(__LINUX__)
12void *_NSConcreteGlobalBlock[32] = {0};
13void *_NSConcreteStackBlock[32] = {0};
14#endif
15static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
16#define defer(exp) \
17 __attribute__((cleanup(__cleanup__))) void ( \
18 ^__CONCAT__(_DEFER_VAR_, __COUNTER__))(void) = ^{ \
19 exp; \
20 }
21#elif defined(__GNUC__)
22#define __DEFER__(exp, COUNTER) \
23 void __CONCAT__(_DEFER_FUNC_, COUNTER)(int *p) { exp; } \
24 int __CONCAT__(_DEFER_VAR_, COUNTER) \
25 __attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
26#define defer(exp) __DEFER__(exp, __COUNTER__)
27#else
28#error "compiler not support!"
29#endif
30
31void ff(int *p) { printf("ff\n"); }
32
33void myfree(char **p) {
34 if (*p) {
35 printf("myfree:%p\n", *p);
36 free(*p);
37 }
38}
39
40int main(int argc, char *argv[]) {
41 FILE *fp = fopen("test.txt", "r");
42 defer(printf("close file\n"); if (fp) fclose(fp));
43 char *__attribute__((cleanup(myfree))) p = malloc(10);
44 char *p1 [[gnu::cleanup(myfree)]] = malloc(10);
45
46 int a [[gnu::cleanup(ff)]] = 10;
47 defer(printf("call defer1, a = %d\n", a));
48 a = 20;
49 defer(printf("call defer2, a = %d\n", a));
50
51 return 0;
52}
CMakeList.txt
1cmake_minimum_required(VERSION 3.25.0)
2project(t)
3
4if(CMAKE_C_COMPILER_ID MATCHES "Clang")
5add_compile_options(
6 -gdwarf-4
7 -fblocks
8)
9endif()
10aux_source_directory(. SRC)
11add_executable(${PROJECT_NAME} ${SRC})
运行结果如下:
有了编译器的这一扩展属性的支持,写C语言也可以减轻程序员释放资源的心智负担了,不用担心某个分支遗忘了释放资源了。而且也可以像C++那样写Lambda 表达式了,而且默认是全部捕获函数内的所有变量。
以上代码在MinGW下使用GCC 14.2
/Clang 18.1.8
、Ubuntu下使用GCC 11.4
/Clang 17.0.6
、MacOS下使用Clang 9.0
编译运行通过。
如果本文对你有帮助,欢迎点赞收藏!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2024/2024-10-26-C语言实现Go的defer功能/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。