C语言实现Go的defer功能

之前笔者写了一篇博文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编译运行通过。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/901378.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【每日一题】LeetCode - 判断回文数

今天我们来看一道经典的回文数题目&#xff0c;给定一个整数 x &#xff0c;判断它是否是回文整数。如果 x 是一个回文数&#xff0c;则返回 true&#xff0c;否则返回 false。 回文数 是指从左往右读和从右往左读都相同的整数。例如&#xff0c;121 是回文&#xff0c;而 123 …

nuxt3项目创建

安装 npx nuxilatest init <project-name> 此时会出现报错&#xff0c;需要在host文件中加入 185.199.108.133 raw.githubusercontent.com 再次执行命令&#xff0c;进入安装 此处选择npm&#xff0c;出现下图表示安装成功 启动项目 执行npm run dev&#xff0c;访…

《皮革制作与环保科技》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《皮革制作与环保科技》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《皮革制作与环保科技》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国轻工业联合会 …

深度学习-循环神经网络-LSTM对序列数据进行预测

项目简介: 使用LSTM模型, 对文本数据进行预测, 每次截取字符20, 对第二十一个字符进行预测, LSTM层: units100, activationrelu Dense层: units输入的文本中的字符种类, 比如我使用的文本有644个不同的字符, 那么units64 激活函数: 因为是多分类, 使用softmax 因为这是最…

已解决 django.db.utils.OperationalError: (1051, “Unknown table

报错信息&#xff1a; django.db.utils.OperationalError: (1051, "Unknown table bjybolg.tool_submission")python manage.py migrate --fake 命令用于告诉 Django 假装已经应用某个迁移&#xff0c;而不实际执行该迁移的操作。这通常在以下情况下非常有用&#x…

【大模型理论篇】大模型压缩技术之注意力层剪枝以及与MLP层联合剪枝

1. 背景分析 本来打算写一篇关于大模型蒸馏的文章&#xff0c;但刚好看到近期发表的一篇讨论大模型压缩的文章【1】&#xff0c;是关于注意力机制冗余性的讨论&#xff0c;比较有意思&#xff0c;作者分析得出并不是所有的注意力都是必须的&#xff0c;可以通过对模型去除冗余的…

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的&#xff1f;在文章开头我们提出这个疑问&#xff0c;带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容&#xff0c;在日常App 中非常常见&#xff0c;比如微博的发布与展示&#xff0c;朋友圈的发布与展示&#xff0c;都在使用富文…

Elasticsearch 中的高效按位匹配

作者&#xff1a;来自 Elastic Alexander Marquardt 探索在 Elasticsearch 中编码和匹配二进制数据的六种方法&#xff0c;包括术语编码&#xff08;我喜欢的方法&#xff09;、布尔编码、稀疏位位置编码、具有精确匹配的整数编码、具有脚本按位匹配的整数编码以及使用 ESQL 进…

Maven 不同环境灵活构建

需求: 使用 Maven根据不同的构建环境&#xff08;如开发、测试、生产&#xff09;来定义不同的配置&#xff0c;实现灵活的构建管理。 需要Demo项目的可以参考&#xff1a;我的demo项目 一、项目分层 一般的初创项目不会有特别多的配置文件&#xff0c;所以使用 spring.profile…

【333基于Java Web的考编论坛网站的设计与实现

毕 业 设 计&#xff08;论 文&#xff09; 考编论坛网站设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计…

linux下gpio模拟spi三线时序

目录 前言一、配置内容二、驱动代码实现三、总结 前言 本笔记总结linux下使用gpio模拟spi时序的方法&#xff0c;基于arm64架构的一个SOC&#xff0c;linux内核版本为linux5.10.xxx&#xff0c;以驱动三线spi(时钟线sclk&#xff0c;片选cs&#xff0c;sdata数据读和写使用同一…

「二叉树进阶题解:构建、遍历与结构转化全解析」

文章目录 根据二叉树创建字符串思路代码 二叉树的层序遍历思路代码 二叉树的最近公共祖先思路代码 二叉搜索树与双向链表思路代码 从前序与中序遍历序列构造二叉树思路代码 总结 根据二叉树创建字符串 题目&#xff1a; 样例&#xff1a; 可以看见&#xff0c;唯一特殊的就…

SCI被「On Hold」意味着什么?会有哪些影响?

本文首发于“生态学者”微信公众号&#xff01; 继Chemosphere在2023年7月被「On Hold」之后&#xff0c;昨晚Science of The Total Environment 被标记为「On Hold」状态在各大公众号和朋友圈被刷屏&#xff01;&#xff08;官方网址&#xff1a;https://mjl.clarivate.com/s…

PouchDB - 免费开源的 JavaScript 数据库,轻量易用,用于离线保存数据的场景

这个 JS 工具库可以让我们很容易地实现数据缓存到本地的需求&#xff0c;要写的代码量也很少。 PouchDB 是一个基于 JavaScript 语言开发的轻量级的数据库&#xff0c;可以在浏览器、Node.js 等环境中使用。作者是一位来自国外的女开发工程师 Alba Herreras。 这是一个运行在浏…

el-datepicker禁用未来日期(包含时分秒)type=‘datetime’

文章目录 实现代码方式1&#xff1a;当选中日期的时候去更新一次。方式2: 优化版本&#xff0c;使用 setTimout 每分钟更新一次。&#xff08;防止选中日期之后过了很久再去选择时分秒时没有根据当前时间去改变&#xff09; el-datepicker 选择器禁用未来日期&#xff0c;动态禁…

重生之“我打数据结构,真的假的?”--2.单链表(无习题)

C语言中的单链表总结 单链表是一种基础的数据结构&#xff0c;广泛应用于C语言编程中。它由节点组成&#xff0c;每个节点包含数据和指向下一个节点的指针。单链表的优点在于动态内存分配和高效的插入与删除操作。本文将详细探讨单链表的定义、基本操作、应用场景以及相关示例…

Gateway 统一网关

一、初识 Gateway 1. 为什么需要网关 我们所有的服务可以让任何请求访问&#xff0c;但有些业务不是对外公开的&#xff0c;这就需要用网关来统一替我们筛选请求&#xff0c;它就像是房间的一道门&#xff0c;想进入房间就必须经过门。而请求想要访问微服务&#xff0c;就必须…

ComfyUI系列——新手安装ComfyUI,就是这么简单

比较Midjoury、WebUI和ComfyUI 在了解ComfyUI的时候&#xff0c;还有其它两款类似的产品&#xff0c;于是就搜集了一下资料&#xff0c;以下是Midjoury、WebUI&#xff08;通常指的是Stable Diffusion Web UI&#xff09;和ComfyUI三者之间的异同点对比表。 特性MidjourneySt…

国内短剧系统源码搭建系统平台小程序新玩法

在数字化内容消费日益普及的今天&#xff0c;短剧小程序作为一种新兴的内容平台&#xff0c;其功能设计至关重要。一个好的短句系统不仅需要提供优质的内容展示&#xff0c;还需要具备一系列优秀功能以满足用户和运营者的需求。以下是一些必备的功能特点&#xff1a; 为大家介…

WebGL 添加背景图

1. 纹理坐标&#xff08;st坐标&#xff09;简介 ST纹理坐标&#xff08;也称为UV坐标&#xff09;是一种二维坐标系统&#xff0c;用于在三维模型的表面上精确地定位二维纹理图像。这种坐标系统通常将纹理的左下角映射到(0,0)&#xff0c;而右上角映射到(1,1)。 S坐标&#x…