C语言实现Go的defer功能

之前笔者写了一篇博文C++实现Go的defer功能,介绍了如何在C++语言中实现Go的defer功能,那在C语言中是否也可以实现这样的功能呢?本文就将介绍一下如何在C语言中实现Go的defer功能。

我们还是使用C++实现Go的defer功能中的示例:

c 复制代码
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属性用法:

c 复制代码
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属性也支持不带参数的函数调用:

c 复制代码
void test()
{
  void ff() { printf("ff called\n"); }
  int z __attribute__((cleanup(ff)));
}

所以要实现前面示例中的写法也很简单,使用宏实现一个嵌套函数来执行自定义的表达式,再定义一个变量带上cleanup属性即可:

c 复制代码
#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属性调用的函数,必须带有一个参数,否则会报错:

bash 复制代码
error: 'cleanup' function 'ff' must take 1 parameter

Clang需要写成:

c 复制代码
void ff(int* p) { printf("ff called\n"); }
void test()
{
  int z __attribute__((cleanup(ff)));
}

但是前面示例的写法是:

c 复制代码
defer(fclose(fp));

函数内部的表达式无法写到外部去,所以就需要借助Clang的Block类型语法扩展了:

c 复制代码
//需要先定义一个外部函数,且必须带一个参数,这个参数的类型是一个`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库:

bash 复制代码
sudo apt install libblocksruntime-dev

如果不安装也可以自己定义一下变量:

c 复制代码
void *_NSConcreteGlobalBlock[32] = {0};
void *_NSConcreteStackBlock[32] = {0};

注意 :MinGW下目前没有可用的BlocksRuntime库,在链接时会报错:

bash 复制代码
undefined reference to `__imp__NSConcreteGlobalBlock'
undefined reference to `__imp__NSConcreteStackBlock'

这就必须自己定义一下变量了:

c 复制代码
void *__imp__NSConcreteGlobalBlock[32] = {0};
void *__imp__NSConcreteStackBlock[32] = {0};

三、跨GCC/Clang编译器

综上,为了让Clang与GCC都能支持前面示例中的写法,可以使用宏来分别处理:

c 复制代码
#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:

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

bash 复制代码
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编译运行通过。

如果本文对你有帮助,欢迎点赞收藏!

相关推荐
TeYiToKu1 小时前
笔记整理—linux驱动开发部分(1)驱动梗概
linux·c语言·arm开发·驱动开发·嵌入式硬件
DdddJMs__1352 小时前
C语言 | Leetcode C语言题解之第517题超级洗衣机
c语言·leetcode·题解
边疆.2 小时前
C++类和对象 (中)
c语言·开发语言·c++·算法
林浔09063 小时前
C语言部分输入输出(printf函数与scanf函数,getchar与putchar详解,使用Linux ubuntu)
c语言·开发语言
blessing。。4 小时前
__attribute__ ((__packed__))
linux·c语言·arm开发
长潇若雪4 小时前
结构体(C 语言)
c语言·开发语言·经验分享·1024程序员节
DARLING Zero two♡5 小时前
关于我、重生到500年前凭借C语言改变世界科技vlog.12——深入理解指针(2)
c语言·开发语言·科技·1024程序员节
环能jvav大师6 小时前
使用Ubuntu系统+VS Code开发STC51单片机
linux·c语言·开发语言·单片机·嵌入式硬件·ubuntu
黄交大彭于晏6 小时前
第五天学习总结:C语言学习笔记 - 数组篇
c语言·笔记·学习
何曾参静谧7 小时前
「C/C++」C++20 之 #include<ranges> 范围
c语言·c++·c++20