之前笔者写了一篇博文C++实现Go的defer功能,介绍了如何在C++语言中实现Go的defer功能,那在C语言中是否也可以实现这样的功能呢?本文就将介绍一下如何在C语言中实现Go的defer功能。
我们还是使用C++实现Go的defer功能中的示例:
void test()
{
FILE* fp = fopen("test.txt", "r");
if (nullptr == fp)
return;
defer(fclose(fp));
if (...)
{
return;
}
if (...)
{
return;
}
if (...)
{
return;
}
}
为了实现该功能,需要借助编译器的扩展功能,GCC/Clang的cleanup
属性,微软目前的编译器不支持该扩展属性,所以本文介绍的方法不适用于微软编译器。
一、GCC编译器
GCC的cleanup
属性,可以参见GCC官方文档var-attr-cleanup-cleanup_function或者文档index-cleanup-variable-attribute。
GCC下的cleanup
属性用法:
static void foo(int *p) { printf("foo called\n"); }
static void bar(int *p) { printf("bar called\n"); }
void baz(void)
{
int x __attribute__((cleanup(foo)));
{ int y __attribute__((cleanup(bar))); }
}
GCC支持函数嵌套,cleanup
属性也支持不带参数的函数调用:
void test()
{
void ff() { printf("ff called\n"); }
int z __attribute__((cleanup(ff)));
}
所以要实现前面示例中的写法也很简单,使用宏实现一个嵌套函数来执行自定义的表达式,再定义一个变量带上cleanup
属性即可:
#define __CONCAT0__(a, b) a##b
#define __CONCAT__(a, b) __CONCAT0__(a, b)
#define __DEFER__(exp, COUNTER) \
void __CONCAT__(_DEFER_FUNC_, COUNTER)() { exp; } \
int __CONCAT__(_DEFER_VAR_, COUNTER) \
__attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
#define defer(exp) __DEFER__(exp, __COUNTER__)
二、Clang编译器
Clang的cleanup
属性,可以参见Clang官方文档。由于Clang目前不支持函数嵌套,但提供了一种叫做Block
类型的语法扩展,参见文档,有点类似C++的Lambda 表达式。
Clang的cleanup
属性调用的函数,必须带有一个参数,否则会报错:
error: 'cleanup' function 'ff' must take 1 parameter
Clang需要写成:
void ff(int* p) { printf("ff called\n"); }
void test()
{
int z __attribute__((cleanup(ff)));
}
但是前面示例的写法是:
defer(fclose(fp));
函数内部的表达式无法写到外部去,所以就需要借助Clang的Block
类型语法扩展了:
//需要先定义一个外部函数,且必须带一个参数,这个参数的类型是一个`Block`类型
static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
void test() {
// 定义一个ff变量,该变量是一个`Block`类型
void (^ff)() = ^{
printf("ff called\n");
};
// 可以像函数一样使用,直接调用
ff();
// 定义一个`Block`类型的x变量,它的值一个是表达式,且拥有`cleanup`属性,调用外部函数__cleanup__
__attribute__((cleanup(__cleanup__))) void (^x)() = ^{
printf("x called\n");
};
}
编译时必须使用-fblocks
参数进行编译,同时链接BlocksRuntime
库。Ubuntu下可以使用下面的命令安装BlocksRuntime
库:
sudo apt install libblocksruntime-dev
如果不安装也可以自己定义一下变量:
void *_NSConcreteGlobalBlock[32] = {0};
void *_NSConcreteStackBlock[32] = {0};
注意:MinGW下目前没有可用的BlocksRuntime
库,在链接时会报错:
undefined reference to `__imp__NSConcreteGlobalBlock'
undefined reference to `__imp__NSConcreteStackBlock'
这就必须自己定义一下变量了:
void *__imp__NSConcreteGlobalBlock[32] = {0};
void *__imp__NSConcreteStackBlock[32] = {0};
三、跨GCC/Clang编译器
综上,为了让Clang与GCC都能支持前面示例中的写法,可以使用宏来分别处理:
#define __CONCAT0__(a, b) a##b
#define __CONCAT__(a, b) __CONCAT0__(a, b)
#if defined(__clang__)
#if defined(__MINGW32__) || defined(__MINGW64__)
void *__imp__NSConcreteGlobalBlock[32] = {0};
void *__imp__NSConcreteStackBlock[32] = {0};
#elif defined(__LINUX__)
void *_NSConcreteGlobalBlock[32] = {0};
void *_NSConcreteStackBlock[32] = {0};
#endif
static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
#define defer(exp) \
__attribute__((cleanup(__cleanup__))) void ( \
^__CONCAT__(_DEFER_VAR_, __COUNTER__))(void) = ^{ \
exp; \
}
#elif defined(__GNUC__)
#define __DEFER__(exp, COUNTER) \
void __CONCAT__(_DEFER_FUNC_, COUNTER)() { exp; } \
int __CONCAT__(_DEFER_VAR_, COUNTER) \
__attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
#define defer(exp) __DEFER__(exp, __COUNTER__)
#else
#error "compiler not support!"
#endif
下面给出完整代码:
main.c:
#include <stdio.h>
#include <stdlib.h>
#define __CONCAT0__(a, b) a##b
#define __CONCAT__(a, b) __CONCAT0__(a, b)
#if defined(__clang__)
#if defined(__MINGW32__) || defined(__MINGW64__)
void *__imp__NSConcreteGlobalBlock[32] = {0};
void *__imp__NSConcreteStackBlock[32] = {0};
#elif defined(__LINUX__)
void *_NSConcreteGlobalBlock[32] = {0};
void *_NSConcreteStackBlock[32] = {0};
#endif
static inline void __cleanup__(void (^*fn)(void)) { (*fn)(); }
#define defer(exp) \
__attribute__((cleanup(__cleanup__))) void ( \
^__CONCAT__(_DEFER_VAR_, __COUNTER__))(void) = ^{ \
exp; \
}
#elif defined(__GNUC__)
#define __DEFER__(exp, COUNTER) \
void __CONCAT__(_DEFER_FUNC_, COUNTER)() { exp; } \
int __CONCAT__(_DEFER_VAR_, COUNTER) \
__attribute__((cleanup(__CONCAT__(_DEFER_FUNC_, COUNTER))))
#define defer(exp) __DEFER__(exp, __COUNTER__)
#else
#error "compiler not support!"
#endif
void ff(int *p) { printf("ff\n"); }
void myfree(char **p) {
if (*p) {
printf("myfree:%p\n", *p);
free(*p);
}
}
int main(int argc, char *argv[]) {
FILE *fp = fopen("test.txt", "r");
defer(printf("close file\n"); if (fp) fclose(fp));
char *__attribute__((cleanup(myfree))) p = malloc(10);
char *p1 [[gnu::cleanup(myfree)]] = malloc(10);
int a [[gnu::cleanup(ff)]] = 10;
defer(printf("call defer1, a = %d\n", a));
a = 20;
defer(printf("call defer2, a = %d\n", a));
return 0;
}
CMakeList.txt
cmake_minimum_required(VERSION 3.25.0)
project(t)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
add_compile_options(
-gdwarf-4
-fblocks
)
endif()
aux_source_directory(. SRC)
add_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
编译运行通过。
如果本文对你有帮助,欢迎点赞收藏!