C语言宏定义实现面向对象,含伪GC
c语言吧
全部回复
仅看楼主
level 5
啥的864 楼主
最近在学m3,m4单片机,都是C语言编程,打了两年的《JVAV》一接触C语言编程环境十分不习惯,打算用C封装一个面向对象基本操作。
目前通过宏定义实现了对象的内存分配,this指针的切换,构造函数的执行等基本功能。
每个类(结构体)都可以通过包含一个通用结构体的方式来实现继承。
由于水平比较菜,GC实现了一个最菜的计数法回收内存,内存不够用的时候根据计数来回收不常用的对象以及该对象动态分配的数据。在单片机环境中运行影响不大,单片机任务注重实时性,长时间不用的内存回收兼容该场景。
2021年05月07日 12点05分 1
level 5
啥的864 楼主
首先是对象内存分配宏定义new()的实现
我定义了两种
一种是带参数的new_args(type,args),相当于带参数的构造函数
一种是不带参数的new(type),相当于无参构造函数
2021年05月07日 13点05分 3
new的基本功能是: 1.先分配内存给对象 2.如果分配成功了就执行构造函数 3.最后通过表达式复合语句返回对象指针
2021年05月07日 13点05分
level 5
啥的864 楼主
运行效果图:
2021年05月07日 13点05分 4
level 5
啥的864 楼主
Main函数调用代码:
2021年05月07日 13点05分 5
第一行和第二行的宏定义是封装了结构体指针的访问方式。 C语言里面结构体指针用->访问,垃圾ide打起来很不爽的,所以通过这个宏定义可以把结构体指针转换成普通的结构体对象访问,这样就能像JVAV一样通过"点"去访问了。
2021年05月07日 13点05分
$()宏定义代码:extern void* this; #define $(n) ({this=n;\ n->counter=0;\ (*n);})
2021年05月07日 13点05分
@啥的864 逻辑比较简单,在你使用这个对象的时候,先设置this为当前对象,然后计数器值清掉,最后返回结构体对象
2021年05月07日 13点05分
level 1
那内存怎么释放啊
2021年05月07日 13点05分 7
我写了GC的,自动回收的
2021年05月07日 13点05分
@啥的864 虽然是个假的GC,单片机跑的话开个定时器执行就行了,PC用的是pthread跑的
2021年05月07日 13点05分
@啥的864 帖子还没写完,等等写这块
2021年05月07日 13点05分
level 5
啥的864 楼主
new实现了之后就是类的结构了,一个类(结构体)中除了你自己要用的函数指针和属性之外,必须额外包括:无参构造函数和有参构造函数以及计数器。
而且由于语法限制,类构造函数名字必须按照如下格式:
例如你的类叫TEST,那构造函数就要叫
void Const_TEST_ARGS(TEST obj,void* str,...);
void Const_TEST(TEST obj);
typedef出来的结构体名字也要按照如下格式:
例如你的类叫TEST,typedef结构体就要叫_TEST
下面放一个TEST的模板和一个List的头文件参考参考:
2021年05月07日 13点05分 8
level 5
啥的864 楼主
最后就是GC了,这块折磨了我亿小会,由于比较菜不知道realloc重新分配会修改之前的地址导致程序一直 -1073740940
先说下基本思路:
1.用一个List存储new出来的对象
2.再用一个List存储这些对象创建的动态内存空间。
3.写一个通用结构体,里面就放一个int型的计数值,通过对象强转从而得到该对象的计数值
4.采用最简单的计数法实现对象回收,长时间不调用的话到点就会执行回收,并free掉对象指针以及该对象分配的动态内存空间。
5.这里长时间不调用会被回收,那么什么是调用呢?
#define list $(LLL)
#define test $(TTT)
TEST TTT=new(TEST);
List LLL=new(List);
只要使用了list.和test.就算是调用,计数器就会清掉,并且把this指针切换成当前对象
或者你可以这样调用$(LLL).和$(TTT)效果是一样的。
上个图:
首先是GC.h
结构很简单,两个List一个用来装new出来的对象,一个用来装这些对象产生的数据
_Data这个结构体中的Belonger用来存储数据的产生者(是哪个对象创建该数据的),Value用来存储数据指针。到时候删除对象的时候,把Belonger和该对象相同的数据也删了。
然后是函数实现:
GC_Init()函数中初始化了两个List并开启了线程用于执行GC
GC_Run()里面不断判断每个对象的计数值,超过了阈值就清掉该对象和对应的数据
GC_Clear()提供给用户主动调用,直接清掉你不要的对象
GC_Run()里面不断遍历对象List,通过把List中的对象强转成通用结构体Template来获取该对象的计数值,如果超过了阈值,就清掉该对象,然后去遍历数据List,通过Belonger判断该对象是否创建了数据,如果有,就把其创建的数据也清掉。
逻辑比较简单,可以正常实现基本功能,基本满足单片机任务中的对象回收需求。
最后放个List类里面有关gc的代码
扩容时:
缩容时:
删除数据:
2021年05月07日 14点05分 9
level 1
用c实现Java?可以,有点意思
2021年05月07日 14点05分 10
工程放出来了,可以看看
2021年05月07日 14点05分
@啥的864 写的比较垃圾
2021年05月07日 14点05分
level 5
啥的864 楼主
当然这代码写出来了肯定是有bug的,水平菜情有可原,说下目前已知的两个bug
第一个是List初始化时,分配的Array大小是sizeof(void*)*10,明显的错误,但是目前没有影响运行,不知道到单片机上会怎么样,这里应该传入sizeof(对象的结构体类型)*10,比较懒惰没改,只能通过全局变量或者传入的方式来操作。
第二个是宏定义BUG,及其影响手感,目前想不到解决方案
#define list $(LLL)
#define test $(TTT)
TEST TTT=new(TEST);
List LLL=new(List);
使用TTT.a=100;或者TTT.b=10;时会报错,因为我宏定义采用的是表达式复合语句,终究还是表达式,表达式众所周知是不可以被赋值的。
如果使用另一种宏定义方法:
#define V(n) this=n;\
n->counter=0;\
(*n)
照样会有bug,bug还比刚才的严重更多,使用这款宏定义后,赋值可以了,但是使用带返回值的函数就会出错,要改也可以,但是代价很大,必须把所有的函数都加上返回值。
代码我直接发出来,带师门帮忙看看
[无效] https://pan.baidu.com/s/1HlZiw0c4vdIunx6GXUPT8g
提取码
ALYA
2021年05月07日 14点05分 11
level 5
啥的864 楼主
有BUG或建议的话随便在这里水贴就行
2021年05月07日 14点05分 12
level 1
代码没怎么看懂[阴险],但是跑的时候发现一些问题。
1. 这个linux下编译没有加 pthread 我这Ubuntu下编译不过cmake里要添加一下 target_link_libraries(Object -lpthread)
2. 创建的线程GC_Init有,GC_Uninit没有,如果程序结束并且并不是所有计数器归零的话有内存泄露吧。
3. 如果数据超时没有使用被释放了,但是在没有通知的情况下,外部怎么确定这个数据不能用了,不然的话正常的数据就这么无预警的释放掉,使用时会产生很难查找的bug。
2021年05月07日 15点05分 13
这点就是无法控制的,只能通过用户设置标志位保护重要的对象和数据不被释放,这问题我太菜了无法解决,只能尽量避免,比如在内存不够用时再按照优先级顺序释放
2021年05月07日 15点05分
@啥的864 用户的数据应该只有用户自己有资格是删除,不然的话分配的数据用户不知道什么时候会被系统释放掉,这挺恐怖的。内存不够还是直接报错来的好。
2021年05月07日 15点05分
level 1
注释掉getchar();后用valgrind --leak-check=full ./Object 运行
2021年05月07日 15点05分 14
1