C语言中的面向切面编程(AOP)

概念

首先给出一段由ChatGPT给出的简短的AOP概念:

AOP是一种编程方法,用来将在程序中多处重复出现的代码(比如日志、权限控制)从主要业务逻辑中抽取出来,提高代码的模块化和可维护性。

抽取后的代码会在原始的业务逻辑代码中特定的位置执行,这些位置由切点(Pointcut)定义。通常会在方法执行前、执行后、抛出异常时等特定点执行抽取出的代码,这些点被称为连接点(Join Point)。

概述

在C语言中,编译器所提供的编译期和执行期的能力相较于java或者其他语言来说会弱一些,这也许就是可能很少听到在C语言中搞面向切面编程的原因之一吧。

从上面的概念上来看,AOP一般是在一些函数(或类方法)执行前后做一些额外处理,例如调用前增加一些权限控制,调用后增加一些日志记录。从这些行为上来说,任何语言其实都可以做到。我们可以简单的在一个函数的开始加一段逻辑或调用某个函数来实现权限验证,在函数返回前调用某个函数添加日志等等。类似如下代码:

void foo(void)
{if (!verify_identity())return;//...log("end");
}

但很显然,这么做会在程序的很多个函数中添加很多重复的代码(例如本例的verify_identitylog),以至于代码变得比较臃肿。

那么有没有什么办法来瘦身呢?

这就是AOP擅长的领域了。

写在示例之前

C语言编译器没有提供很完整的AOP支持,因此我们需要自行手动实现,或者使用一些现有的库来实现。

本文将使用开源C语言库Melon的函数模板来实现上面的效果。

在Melon提供的函数模板组件中,实现了若干宏函数,这些宏函数都是用来定义不同类型的函数的。这些用宏来定义的函数和我们原生C语言中的函数的区别,简单来说就是,在我们实际要执行的函数逻辑外,再封装一个函数,这个函数会在我们指定的函数逻辑开始前和结束后调用一个回调函数(即函数的入口回调函数出口回调函数)。

基于函数模板的这一特性,Melon中实现了一个span组件,用来度量使用函数模板定义的函数的时间开销。

但如果事情仅限于此,那么这种AOP很显然能做到的事情也基本仅限于此了。

因此,Melon支持了c99,并利用c99提供的宏特性,实现了将函数模板定义的函数的实参以可变参数的形式传递到入口和出口回调函数中。这就意味着,入口和出口回调函数可以访问函数的参数,并对参数的内容作出修改(主要针对指针指向的内存中的数据)。

这样,就给我们在回调函数中提供了更多的可操作空间。我们可以针对不同的函数,修改其参数值,从而来影响后续函数调用中的执行逻辑。例如前面的权限验证,我们可以将其大致简化为如下形式:

void entry_callback(char *file, char *func, int line, ...)
{va_list args;va_start(args, line);int *a = (int *)va_arg(args, int *);va_end(args);if (!verify_identity())*a = 0;
}void exit_callback(char *file, char *func, int line, ...)
{va_list args;va_start(args, line);int *a = (int *)va_arg(args, int *);va_end(args);log("%d\n", *a);
}void foo(int *a)
{if (!*a)return;//...
}int bar(int *d, int e)
{if (!*d)return -1;//...return 0;
}

这里的代码只是一个示意,后面会给出一个实际可用的示例。

我们可以随意增加函数,这些函数都会利用同一对入口和出口函数来实现身份验证。

示例

下面就给出一个可用的使用函数模板实现AOP的C语言代码。

//a.c#include "mln_func.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>MLN_FUNC_VOID(static, void, foo, (int *a, int b), (a, b), {printf("in %s: %d\n", __FUNCTION__, *a);*a += b;
})MLN_FUNC(static, int, bar, (void), (), {printf("%s\n", __FUNCTION__);return 0;
})static void my_entry(const char *file, const char *func, int line, ...)
{if (strcmp(func, "foo"))return;va_list args;va_start(args, line);int *a = (int *)va_arg(args, int *);va_end(args);printf("entry %s %s %d %d\n", file, func, line, *a);++(*a);
}static void my_exit(const char *file, const char *func, int line, ...)
{if (strcmp(func, "foo"))return;va_list args;va_start(args, line);int *a = (int *)va_arg(args, int *);va_end(args);printf("exit %s %s %d %d\n", file, func, line, *a);
}int main(void)
{int a = 1;mln_func_entry_callback_set(my_entry);mln_func_exit_callback_set(my_exit);foo(&a, 2);return bar();
}

这段函数中,我们使用MLN_FUNCMLN_FUNC_VOID来定义了两个函数,即foobar。两个函数的逻辑很简单,就是printf输出当前函数名以及参数值(如果有参数的话)。同时,我们也使用了mln_func_entry_callback_setmln_func_exit_callback_set定义了两个全局回调函数,用来在函数调用开始和结束时调用。

我们可以看到,回调函数中使用strcmp对进入回调的函数做了过滤,仅对foo函数做额外处理。在入口回调中输出函数信息及第一个参数的值,随后修改参数指针指向的内存中的值。在出口回调中输出函数信息和参数值。

我们来编译一下(我们假定这个代码文件名为a.c):

cc -o a a.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -std=c99 -DMLN_C99 -DMLN_FUNC_FLAG

这里:

  • /usr/local/melon是Melon库的默认安装路径。
  • -std=c99是启用c99。
  • -DMLN_C99是定义一个名为MLN_C99的宏,这个宏用来启用函数模板组件中C99下才有的特性。
  • -DMLN_FUNC_FLAG用来定义一个名为MLN_FUNC_FLAG的宏,这个宏用来启用函数模板功能。是的,如果没有这个宏,上面的那些使用MLN_FUNC定义的函数就是普通的C语言函数,也不会触发入口和出口回调函数的调用。

执行一下看看效果:

entry a.c foo 6 1
in __mln_func_foo: 2
exit a.c foo 6 4
__mln_func_bar

可以看到:

  • 入口回调函数中,foo的第一个参数指向的内存中的值为1
  • 进入foo的实际函数逻辑中,printf输出当前的函数名为__mln_func_foo,以及此时看到的第一个参数的值为2,不再是1了,因为在入口回调函数中被修改了。__mln_func_foo这个函数执行的才是我们定义的逻辑,而foo是对__mln_func_foo的一个封装。
  • 出口回调函数中,我们看到第一个参数的值变为了4,因为它在我们给出的函数逻辑中做了修改。
  • 最后输出的是bar的实际执行逻辑所在的函数名,与foo的形式一致。

最后,我们去掉MLN_FUNC_FLAG这个宏再次编译一次:

cc -o a a.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -std=c99 -DMLN_C99

然后执行一下看看输出结果:

in foo: 1
bar

可以看得出,此时foobar不再是封装函数,而是我们定义的函数逻辑的函数名,即普通的C语言函数。

读到这里的都是真爱,感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

✅ Windows11 系统 I 卡独显 A770 安装 深度学习 Pytorch 环境

&#x1f4cb; 文献参考 这里非常感谢知乎上的 ‘丢丢’ 的[**Windows系统下英特尔独显Pytorch的历程**] 为我提供了一开始的 I 卡安装想法&#xff01;但是文中并未介绍如何进行额外的环境变量操作问题&#xff0c;导致很多软件直接安装至系统盘&#xff0c;占用系统盘空间&am…

网络、UDP编程

1.网络协议模型: OSI协议模型 应用层 实际发送的数据 表示层 发送的数据是否加密 会话层 是否建立会话连接 传输层 数据传输的方式&#xff08;数据报、流式&#xff09; 网络层 …

gitlab重点知识CI/CD详细步骤说明

介绍 GitHub因其庞大的社区和对开源项目的友好支持而广受欢迎,而GitLab则因其企业级特性和私有仓库的便利性而在企业内部得到广泛应用。 GitLab是一个开源的仓库管理系统,它使用Git作为代码管理工具,并提供了基于Web的服务。 GitLab具有以下特点: 集成了版本控制、持续集…

19.相机,棱镜和光场

一、成像方法 Imaging Synthesis Capture 1.Synthesis&#xff08;图形学上&#xff09;合成&#xff1a;比如之前学过的光线追踪或者光栅化 2.Capture&#xff08;捕捉&#xff09;&#xff1a;把真实世界存在的东西捕捉成为照片 二、相机 1.小孔成像 利用小孔成像的相…

0102全排列和对换-行列式-线性代数

把n个不同的数排成一列&#xff0c;叫做这n个数的全排列&#xff08;排列&#xff09;。 一般情况&#xff0c; 1 , 2 , ⋯ , n 1,2,\cdots,n 1,2,⋯,n是n个数排列的标准次序。 当n个数的任一排列中两个数的先后次序与标准次序不同时&#xff0c;有说有一个逆序。 一个排列中所…

【数据结构】矩阵的压缩存储

矩阵的压缩存储 5.1 普通矩阵的存储 用二维数组存储 分为行优先和列优先&#xff1a; 行优先&#xff1a;优先存放一行的数据。 列优先&#xff1a;优先存放一列的数据。 注意下标是从0还是1开始的&#xff01; 5.2 对称矩阵的存储 对称矩阵定义 若n阶方阵中任意一个元素 a i …

Vue template到render过程

Vue template到render过程 vue的模版编译过程主要如下&#xff1a;template -> ast -> render函数&#xff08;1&#xff09;调用parse方法将template转化为ast&#xff08;抽象语法树&#xff09;&#xff08;2&#xff09;对静态节点做优化&#xff08;3&#xff09;生…

Sqoop “hcatalog does not exist!” 提示信息消除方法

sqoop运行的时候老是有这个报错提示&#xff0c;看着可烦&#xff0c;解决消除一下 解决方法&#xff1a; 1、在$SQOOP_HOME/bin目录下面修改configure-sqoop文件 1&#xff09;进文件夹 cd /training/sqoop-1.4.7/bin2&#xff09;编辑文件 vi /configure-sqoop3&#xff…

K8s Pod控制器

目录 前言&#xff1a; 1.Deployment 查看控制器配置 查看历史版本 2.SatefulSet 安装CoreDNS&#xff0c;仅二进制部署环境需要安装CoreDNS 方法一 方法二 查看statefulset的定义 清单定义StatefulSet 创建pv 定义PV 创建statefulset 滚动更新 总结 扩展伸缩…

【MySQL | 第三篇】MySQL索引及两种索引分类方法总结

文章目录 3.MySQL索引及两种索引分类方法3.1索引的概念3.1.1相关定义3.1.2查询例子 3.2索引的底层3.2.1二叉树&#xff08;1&#xff09;满二叉树&#xff08;2&#xff09;完全二叉树&#xff08;3&#xff09;二叉查找树&#xff08;4&#xff09;二叉平衡树&#xff08;AVL&…

HTML静态网页成品作业(HTML+CSS)——电影网首页网页设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

Vue3_2024_6天【回顾上篇watch常见的前三种场景】另两种待补

第一种情况&#xff1a;监视【ref】定义&#xff08;基本数据类型&#xff09; 1.引入watch2.格式&#xff1a;watch&#xff08;基本数据类型数据&#xff0c;监视变化的回调函数&#xff09; 注意点&#xff1a; 2.1.watch里面第一个参数&#xff0c;是数据~~【监视的基本类…

[Buuctf] [MRCTF2020] Xor

运行 1.查壳 32位exe文件&#xff0c;没有壳 2.用32位IDA打开 找到main函数&#xff0c;F5查看伪代码&#xff0c;但是这里会弹出一个窗口 函数分析失败&#xff01;&#xff01; 这里我在看别人的题解时发现一种玄学方式解决了这个问题 窗口里面弹出了一个地址401095&…

LVS+Keepalived 高可用负载均衡集群

一. 高可用集群的相关知识 1.1 高可用&#xff08;HA&#xff09;集群和普通集群的比较 ① 普通集群 普通的群集的部署是通过一台度器控制调配多台节点服务器进行业务请求的处理&#xff0c;但是仅仅是一台调度器&#xff0c;就会存在极大的单点故障风险&#xff0c;当该调度…

蓝桥杯备赛之二分专题

常用的算法二分模板 1. 在数组a[]中找大于等于x的第一个数的下标 //int ans lower_bound(a, a n, x) - a //相当于下方 int l 0, r n - 1; while(l < r) {int mid l r >> 1;if(a[mid] > x) r mid;else l mid 1; } cout << r;2. 在数组a[]中找大于…

详解Rust的连贯性和孤儿规则

最近学习Rust时候看到两个术语&#xff1a;连贯性(Coherence)和孤儿规则(Orphan rules)&#xff0c;书上解释的不是很清楚&#xff0c;又没有给出具体的代码示例&#xff0c;让人很难理解。我在网上搜了好久&#xff0c;最后又查了Rust语言规范&#xff0c;算是搞明白了这两个概…

qml中toolbox控件、ComboBox控件、PlainText实现及美化

一. 内容简介 qml中toolbox控件、ComboBox控件、PlainText实现及美化 二. 软件环境 2.1vsCode 2.2Anaconda version: conda 22.9.0 2.3pytorch 安装pytorch(http://t.csdnimg.cn/GVP23) 2.4QT 5.14.1 新版QT6.4,&#xff0c;6.5在线安装经常失败&#xff0c;而5.9版本…

[蓝桥杯 2019 省 B] 等差数列

题目链接 [蓝桥杯 2019 省 B] 等差数列 题目描述 数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列&#xff0c;只记得其中 N N N 个整数。 现在给出这 N N N 个整数&#xff0c;小明想知道包含这 N N N 个整数的最短的等差数列有几项&#x…

亚马逊运营要使用什么海外代理IP?

代理IP作为网络活动的有力工具&#xff0c;同时也是跨境电商的必备神器。亚马逊作为跨境电商的头部平台&#xff0c;吸引了大量的跨境电商玩家入驻&#xff0c;想要做好亚马逊&#xff0c;养号、测评都需要代理IP的帮助。那么应该使用什么代理IP呢&#xff1f;如何使用&#xf…

复杂系统未来演化很难准确预测

复杂系统的未来演化很难准确预测。这是由于复杂系统包含大量相互作用的组成部分&#xff0c;其行为和发展受到多种因素的影响&#xff0c;包括内部动力、外部环境变化以及意外事件等。此外&#xff0c;复杂系统通常呈现非线性关系&#xff0c;即系统的变化可能以不可预测的方式…