一个极简鲁棒的C语言的动态数据类型扩展,取代诸如C++/Rust那些愚蠢的东西

本文主要是介绍一个极简鲁棒的C语言的动态数据类型扩展,取代诸如C++/Rust那些愚蠢的东西,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目地址:https://github.com/shajunxing/banana-cvar

我用过很多高级语言,喜欢简单的东西,讨厌C++,一直在想C语言能不能用最简洁的手段扩充动态语言特性,并且支持垃圾回收呢?偶然迸发出灵感,网上查了查没有人做过,那么开始动手吧。

思路很简单:

  1. 首先定义存储动态变量的结构体var,变量类型简单起见,参照Json标准,null/boolean/number/array/object足够用了,动态变量的使用均采用指针指向堆上分配内存的方式,所有动态变量都托管在pvarroot全局链表上。
  2. 然后定义存储栈变量信息的结构体ref,栈变量就是c源代码里定义的var *变量,称之为引用,通过vdeclare/vassign宏登记引用的地址以及指向动态变量的地址,登记在全局链表prefroot上。
  3. 最后最关键的,在垃圾收集阶段,如果引用现在指向的地址与先前登记指向的地址不同,就意味着该引用已失效(比如函数调用返回后栈值变化了、或者人为修改了),执行删除操作,把有效引用指向的动态变量(array/object需要递归)标记为正在使用中,然后对未使用的动态变量执行删除操作。
  4. 后续按需扩充功能,考虑设计一套扩展的动态语法,并写个翻译器翻译成c代码。
  5. 全局变量很少,如果需要多线程支持,可以包进结构体里并作为函数参数传递,但我认为没有必要,且每个线程一套,又不能相互交互,怎么看怎么别扭。

为了遵循大部分动态语言的使用习惯,规定以下的设计准则:

  1. 务必用宏vdeclare/vassign/var创建动态变量,如无必要,勿直接操作var *指针,除非你知道自己在做什么;游离在外的var *指针没有调用refer登记为引用的,相当于弱引用,垃圾回收时候会被删除;未分配的var *指针(指向NULL)在大部分函数里都会异常退出。
  2. 将a变量赋值给b变量,实际传递的是地址;为了节省内存,null/true/false都是全局常量;所有赋number/string原始值的,都是创建新变量,而非修改原变量;array/object另有一套增删改的函数。
  3. 对于异常处理,所有无法恢复的错误均执行exitif宏退出进程。
  4. 我也忘了使用到C语言最新标准的什么特性,反正最新版本GCC编译通过就是了,包含MinGW的makefile,别的操作系统自行修改。
  5. 这不是玩具,我的想法是用它替代C++/Rust之类走入歧途的复杂东西,反正我测试过程中还没有发现内存泄露等严重问题。
  6. 函数命名规范:可选缩写+完整单词+可选后缀,缩写的含义如下,当前用到的后缀只有一个,_s表示字符串参数带长度参数的安全函数,C语言字符串字面量可以安全使用不带_s的函数,别的情况建议用带_s的。
缩写说明
v动态类型的变量
r引用
zzero、zilch、zip,表示null,参见https://www.englishtrackers.com/english-blog/zero-zilch-zip-nil-nought-nothing-whats-the-difference/
bboolean
nnumber
sstring
aarray
oobject
wworld、whole,表示整体的、全局的
sbstringbuffer辅助工具

以下是函数/宏列表,以及对应的常见动态语言的语法(你一看就懂),和举例:

函数/宏动态语法说明/例子
dump()打印所有常量、变量和引用信息
void gc()垃圾回收
vassign(a, b)a = b
vdeclare(a, b)var a = b
struct var *znew()返回指向null常量的地址
zdeclare(a)var a = null
struct var *bnew(bool b)返回指向true/false常量的地址
bdeclare(a, b)var a = truebdeclare(a, true)
bool bvalue(struct var *pv)返回boolean变量的原始值
struct var *nnew(double n)创建number变量
ndeclare(a, b)var a = 3.14ndeclare(a, 3.14)
double nvalue(struct var *pv)返回number变量的原始值
var *snew_s(const char *s, size_t slen)
var *snew(const char *sz)
创建string变量
sdeclare(a, b)var a = “hello”sdeclare(a, “hello”)
char *svalue(struct var *pv)返回string变量的原始值(指向\0结尾的字符数组)
size_t slength(struct var *pv)a.length()返回字符串长度
struct var *sconcat(size_t num, …)var a = “hi”
var b = “all”
var c = a + b
sdeclare(a, “hi”)
sdeclare(b, “all”)
vdeclare(c, sconcat(2, a, b))
var *anew(size_t num, …)var a = [null, true, 3.14, “hello”]创建包含0个或多个成员的array变量
vdeclare(a, anew(4, znew(), bnew(true), nnew(3.14), snew(“hello”)))
adeclare(a)var a = []
void aclear(struct var *pv)v.clear()清空数组
size_t alength(struct var *pv)v.length()返回数组长度
void apush(struct var *pv, struct var *pval)v.push(val)数组末端插入,长度+1
struct var *apop(struct var *pv)v.pop()数组末端弹出,长度-1
void aput(struct var *pv, size_t idx, struct var *pval)v[idx] = val数组指定下标写入替换原有值,下标越界则程序退出
struct var *aget(struct var *pv, size_t idx)v[idx]取数组指定下标的值,下标越界则程序退出
asort(…)数组排序,未实现
struct var *onew()创建空对象
odeclare(a)var a = {}
void oclear(struct var *pv)v.clear()清空对象
size_t olength(struct var *pv)v.length()返回对象的元素数量
void oput_s(struct var *pv, const char *key, size_t klen, struct var *pval)
void oput(struct var *pv, const char *key, struct var *pval)
v[key] = val写入键值对
struct var *oget_s(struct var *pv, const char *key, size_t klen)
struct var *oget(struct var *pv, const char *key)
v[key]读取键对应的值,注意可能返回NULL,程序务必要做判断
struct var *vtojson(struct var *pv)json化任意变量,结果以字符串变量返回,注意:禁止套娃,循环嵌套的忽略
tojson(pv)print([null, [3.140000, “hi”], false])返回变量json化后的C字符数组,用于printf等函数
printf(tojson(anew(3, znew(), anew(2, nnew(3.14), snew(“hi”)), bnew(false))))
fromjson(…)解析json字符串,构建变量,未实现。

更多示例代码,参见test_var.c

这篇关于一个极简鲁棒的C语言的动态数据类型扩展,取代诸如C++/Rust那些愚蠢的东西的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902668

相关文章

C语言进阶(预处理命令详解)

《C语言进阶(预处理命令详解)》文章讲解了宏定义规范、头文件包含方式及条件编译应用,强调带参宏需加括号避免计算错误,头文件应声明函数原型以便主函数调用,条件编译通过宏定义控制代码编译,适用于测试与模块... 目录1.宏定义1.1不带参宏1.2带参宏2.头文件的包含2.1头文件中的内容2.2工程结构3.条件编

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Go语言并发之通知退出机制的实现

《Go语言并发之通知退出机制的实现》本文主要介绍了Go语言并发之通知退出机制的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、通知退出机制1.1 进程/main函数退出1.2 通过channel退出1.3 通过cont

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

详解MySQL中JSON数据类型用法及与传统JSON字符串对比

《详解MySQL中JSON数据类型用法及与传统JSON字符串对比》MySQL从5.7版本开始引入了JSON数据类型,专门用于存储JSON格式的数据,本文将为大家简单介绍一下MySQL中JSON数据类型... 目录前言基本用法jsON数据类型 vs 传统JSON字符串1. 存储方式2. 查询方式对比3. 索引

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被