转载需经作者同意,并注明出处
前言
我的一块板子,好像不支持多线程。于是我想到了python中的生成器。
就是这个MCU:ATMEGA168P
文章看着玩玩而已,仅供学习和娱乐。
正文
首先定义一个测试的函数。(姑且叫它生成器好了)
功能是从1数到a。(类似python的range
)
int func(void** func_save, char *func_flag, int a){return 0;}
参数中的func_save
是这个生成器的参数与局部变量的保存区域,func_flag
是它的运行状态。
这里简单地将0
作为起始状态,1
作为运行状态,2
作为结束状态。
这个结构体就是用来保存生成器的东东。
// The data of the running function.
typedef struct {int arg_0; int counter;} func_object;
一开始,初始化的时候在内存中开辟一块区域,存放生成器的参数与局部变量。
if (!*func_save && !(*func_flag)) {
*func_save = calloc(1, sizeof(func_object));
((func_object*)*func_save) -> arg_0 = a;
*func_flag = 1;
}
当然有开始就要有结束,做事不能够虎头蛇尾。
// Exit status.
if(((func_object*)*func_save) -> counter >= ((func_object*)*func_save) -> arg_0){
free(*func_save);
*func_save = NULL;
*func_flag = 2;
}
然后在这两片代码中间插入主要的代码。
如果使用类似状态机或断点的设计,则需要在结构体中存放相应的状态
(使用时通常用switch...case...
)
// Execute the main function.
((func_object*)*func_save) -> counter ++;
然后在函数的末尾将生成结果返回。(把最开始的)return 0
去掉
// Yield value.
return ((func_object*)*func_save) -> counter;
这样一个简易的生成器就造好了
有什么问题呢?
我们先写一段代码测试一下
int main(void){
void *f1 = 0,*f2 = 0;
char f1_flag = 0, f2_flag = 0;
do{
printf("func 1: %d, ",func(&f1, &f1_flag, 5));
printf("func 2: %d\n",func(&f2, &f2_flag, 10));
}while(f1_flag < 2 || f2_flag < 2);
return 0;
}
然后,熟悉的 Segmentation fault 出现了,wow
这是为什么呢?
很简单,因为在生成器生成完之后(即counter >= a
时),释放生成器的状态。
释放完了,然后返回的时候又调用了被释放的内存。
于是后果可想而知。
此时生成器是在操作完毕之后才返回的,而非在操作之前就判断,整体结构类似于do...while
。(感兴趣的可以调换这两片代码的位置)
而此时最后一个生成结果还没有返回,我们需要创建一个临时变量让它返回。
在定义结构体的代码的下方定义一个临时变量,这里叫它ret
好了
int ret;
修改结束时的代码
// Exit status.
if(((func_object*)*func_save) -> counter >= ((func_object*)*func_save) -> arg_0){
// 这里,储存最后一个返回值
ret = ((func_object*)*func_save) -> counter;
free(*func_save);
*func_save = NULL;
*func_flag = 2;
// 这里,返回
return ret;
}
这次应该没有问题了…?
然鹅,事与愿违。
理由也很简单,虽然生成器已经生成结束(已释放内存),但是再次调用时却没有判断生成器是否已经搞完了。
于是乎,在主要代码的部分又双又叕又又又又又调用了被释放的内存。
在执行主要的代码之前,还需要判断生成器的状态。
// When finished.
if(*func_flag == 2) return 0;
于是,这一小撮代码就大功告成了。
这回能够正确地执行了
注意:虽然func1
在结束后一直返回0
,但是在其他地方可能就会出现“手持两把锟斤拷,口中疾呼烫烫烫”了,所以建议对ret
变量进行初始化。
完整代码:
/*
* Virtual Threading.
* @author: Qiong-Mengzi(Karyna Sakura/かりなさくら)
*/
#include <stdio.h>
#include <stdlib.h>
int func(void** func_save, char *func_flag, int a){
// The data of the running function.
typedef struct {int arg_0; int counter;} func_object;
int ret;
if (!*func_save && !(*func_flag)) {
*func_save = calloc(1, sizeof(func_object));
((func_object*)*func_save) -> arg_0 = a;
*func_flag = 1;
}
// When finished.
if(*func_flag == 2) return 0;
// Execute the main function.
((func_object*)*func_save) -> counter ++;
// Exit status.
if(((func_object*)*func_save) -> counter >= ((func_object*)*func_save) -> arg_0){
ret = ((func_object*)*func_save) -> counter;
free(*func_save);
*func_save = NULL;
*func_flag = 2;
return ret;
}
// Yield value.
return ((func_object*)*func_save) -> counter;
}
int main(void){
void *f1 = 0,*f2 = 0;
char f1_flag = 0, f2_flag = 0;
do{
printf("func 1: %d, ",func(&f1, &f1_flag, 5));
printf("func 2: %d\n",func(&f2, &f2_flag, 10));
}while(f1_flag < 2 || f2_flag < 2);
return 0;
}
唔,移植的问题嘛,虽然我又自己的内存池来代替calloc(使用标记-压缩的GC算法),但是懒得调试了。
直接上效果(这里我对输出的部分做了修改,生成器生成结束后不输出)
代码:
#include <stdlib.h>
int func(void** func_save, char *func_flag, int a){
// The data of the running function.
typedef struct {int arg_0; int counter;} func_object;
int ret;
if (!*func_save && !(*func_flag)) {
*func_save = calloc(1, sizeof(func_object));
((func_object*)*func_save) -> arg_0 = a;
*func_flag = 1;
}
// When finished.
if(*func_flag == 2) return 0;
// Execute the main function.
((func_object*)*func_save) -> counter ++;
// Exit status.
if(((func_object*)*func_save) -> counter >= ((func_object*)*func_save) -> arg_0){
ret = ((func_object*)*func_save) -> counter;
free(*func_save);
*func_save = NULL;
*func_flag = 2;
return ret;
}
// Yield value.
return ((func_object*)*func_save) -> counter;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Start.");
void *f1 = 0,*f2 = 0;
char f1_flag = 0,f2_flag = 0;
do{
if(f1_flag < 2){
Serial.print("VThread 1: ");
Serial.print(func(&f1, &f1_flag, 5));
}
if(f2_flag < 2){
Serial.print(" VThread 2: ");
Serial.print(func(&f2, &f2_flag, 10));
}
Serial.println();
}while(f1_flag < 2 || f2_flag < 2);
Serial.println("Finish.");
}
void loop() {
// put your main code here, to run repeatedly:
}
开发板:Arduino Nano (ATMEGA168P,只有可怜的1KiB内存)
我的Github:https://github.com/Qiong-Mengzi
当然,这个测试代码是不可能放在上面的()