嵌入式状态机的几种骚操作_嵌入式状态机的妙用,2024年最新Golang数据结构算法面试题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

void bomb2_dispatch(EVT_TYPE evt , void* param)
{
fp_state s = NULL;
if(evt > MAX_EVT)
{
LOG(“EVT type error!”);
return;
}
s = bomb2.state_table[bomb2.state * MAX_EVT + evt];
if(s != NULL)
{
s(evt , param);
}
}
/列出所有的状态对应的事件处理函数/
void setting_UP(EVT_TYPE evt, void* param)
{
if(bomb1.timeout< 60) ++bomb1.timeout;
bsp_display(bomb1.timeout);
}

缺点:函数粒度太小是最明显的一个缺点,一个状态和一个事件就会产生一个函数,当状态和事件较多时,处理函数将增加很快,在阅读代码时,逻辑分散。没有实现进入退出动作。

一维状态转换表

图片

实现原理:图片

typedef void (fp_action)(EVT_TYPE evt,void param);

/转换表基础结构/
struct tran_evt_t
{
EVT_TYPE evt;
uint8_t next_state;
};
/状态的描述/
struct fsm_state_t
{
fp_action enter_action; //进入动作
fp_action exit_action; //退出动作
fp_action action;

tran_evt_t* tran; //转换表
uint8_t tran_nb; //转换表的大小
const char* name;
}
/状态表本体/
#define ARRAY(x) x,sizeof(x)/sizeof(x[0])
const struct fsm_state_t state_table[]=
{
{setting_enter , setting_exit , setting_action , ARRAY(set_tran_evt),“setting” },
{timing_enter , timing_exit , timing_action , ARRAY(time_tran_evt),“timing” }
};

/构建一个状态机/
struct fsm
{
const struct state_t * state_table; /* the State-Table /
uint8_t cur_state; /
the current active state */

uint8_t timeout;
uint8_t code;
uint8_t defuse_code;
}bomb3;

/初始化状态机/
void bomb3_init(void)
{
bomb3.state_table = state_table; //指向状态表
bomb3.cur_state = setting;
bomb3.defuse_code = 8; //1000
}
/状态机事件派发/
void fsm_dispatch(EVT_TYPE evt , void* param)
{
tran_evt_t* p_tran = NULL;

/获取当前状态的转换表/
p_tran = bomb3.state_table[bomb3.cur_state]->tran;

/判断所有可能的转换是否与当前触发的事件匹配/
for(uint8_t i=0;i<x;i++)
{
if(p_tran[i]->evt == evt)//事件会触发转换
{
if(NULL != bomb3.state_table[bomb3.cur_state].exit_action){
bomb3.state_table[bomb3.cur_state].exit_action(NULL); //执行退出动作
}
if(bomb3.state_table[_tran[i]->next_state].enter_action){
bomb3.state_table[_tran[i]->next_state].enter_action(NULL);//执行进入动作
}
/更新当前状态/
bomb3.cur_state = p_tran[i]->next_state;
}
else
{
bomb3.state_table[bomb3.cur_state].action(evt,param);
}
}
}
/*************************************************************************
setting状态相关
***********************************************************************/
void setting_enter(EVT_TYPE evt , void
param)
{

}
void setting_exit(EVT_TYPE evt , void* param)
{

}
void setting_action(EVT_TYPE evt , void* param)
{

}
tran_evt_t set_tran_evt[]=
{
{ARM , timing},
}
/timing 状态相关/

优点:

  • 各个状态面向用户相对独立,增加事件和状态不需要去修改先前已存在的状态事件函数。
  • 实现了状态的进入和退出
  • 容易根据状态跃迁图来设计 (状态跃迁图列出了每个状态的跃迁可能,也就是这里的转换表)
  • 实现灵活,可实现复杂逻辑,如上一次状态,增加监护条件来减少事件的数量。可实现非完全事件驱动

缺点:

  • 函数粒度较小(比二维小且增长慢),可以看到,每一个状态需要至少3个函数,还需要列出所有的转换关系。

5. QP嵌入式实时框架

事件驱动型编程

好莱坞原则:和传统的顺序式编程方法例如“超级循环”,或传统的RTOS 的任务不同。绝大多数的现代事件驱动型系统根据好莱坞原则被构造(Don’t call me; I’ll call you.)

面向对象

类和单一继承:

工具

QM :一个通过UML类图来描述状态机的软件,并且可以自动生成C代码

QS软件追踪工具:

6. QEP实现有限状态机Fsm

/* qevent.h ----------------------------------------------------------------/
typedef struct QEventTag
{
QSignal sig;
uint8_t dynamic_;
} QEvent;
/
qep.h -------------------------------------------------------------------/
typedef uint8_t QState; /
status returned from a state-handler function */
typedef QState (*QStateHandler) (void *me, QEvent const e); / argument list /
typedef struct QFsmTag /
Finite State Machine /
{
QStateHandler state; /
current active state */
}QFsm;

#define QFsm_ctor(me_, initial_) ((me_)->state = (initial_))
void QFsm_init (QFsm *me, QEvent const *e);
void QFsm_dispatch(QFsm *me, QEvent const *e);

#define Q_RET_HANDLED ((QState)0)
#define Q_RET_IGNORED ((QState)1)
#define Q_RET_TRAN ((QState)2)
#define Q_HANDLED() (Q_RET_HANDLED)
#define Q_IGNORED() (Q_RET_IGNORED)

#define Q_TRAN(target_) (((QFsm *)me)->state = (QStateHandler) (target_),Q_RET_TRAN)

enum QReservedSignals
{
Q_ENTRY_SIG = 1,
Q_EXIT_SIG,
Q_INIT_SIG,
Q_USER_SIG
};

/* file qfsm_ini.c ---------------------------------------------------------/
#include “qep_port.h” /
the port of the QEP event processor /
#include “qassert.h” /
embedded systems-friendly assertions */
void QFsm_init(QFsm *me, QEvent const *e)
{
(me->state)(me, e); / execute the top-most initial transition /
/
enter the target /
(void)(me->state)(me , &QEP_reservedEvt_[Q_ENTRY_SIG]);
}
/
file qfsm_dis.c ---------------------------------------------------------
/
void QFsm_dispatch(QFsm *me, QEvent const e)
{
QStateHandler s = me->state; /
save the current state */
QState r = (s)(me, e); / call the event handler /
if (r == Q_RET_TRAN) /
transition taken? */
{
(void)(s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]); / exit the source */
(void)(me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);/enter target/
}
}
实现上面定时器例子
#include “qep_port.h” /
the port of the QEP event processor /
#include “bsp.h” /
board support package */

enum BombSignals /* all signals for the Bomb FSM /
{
UP_SIG = Q_USER_SIG,
DOWN_SIG,
ARM_SIG,
TICK_SIG
};
typedef struct TickEvtTag
{
QEvent super; /
derive from the QEvent structure /
uint8_t fine_time; /
the fine 1/10 s counter */
}TickEvt;

typedef struct Bomb4Tag
{
QFsm super; /* derive from QFsm /
uint8_t timeout; /
number of seconds till explosion /
uint8_t code; /
currently entered code to disarm the bomb /
uint8_t defuse; /
secret defuse code to disarm the bomb */
} Bomb4;

void Bomb4_ctor (Bomb4 *me, uint8_t defuse);
QState Bomb4_initial(Bomb4 *me, QEvent const e);
QState Bomb4_setting(Bomb4 me, QEvent const e);
QState Bomb4_timing (Bomb4 me, QEvent const e);
/
--------------------------------------------------------------------------
/
/
the initial value of the timeout /
#define INIT_TIMEOUT 10
/
/
void Bomb4_ctor(Bomb4 me, uint8_t defuse) {
QFsm_ctor_(&me->super, (QStateHandler)&Bomb4_initial);
me->defuse = defuse; /
the defuse code is assigned at instantiation /
}
/
/
QState Bomb4_initial(Bomb4 me, QEvent const e) {
(void)e;
me->timeout = INIT_TIMEOUT;
return Q_TRAN(&Bomb4_setting);
}
/
/
QState Bomb4_setting(Bomb4 me, QEvent const e) {
switch (e->sig){
case UP_SIG:{
if (me->timeout < 60) {
++me->timeout;
BSP_display(me->timeout);
}
return Q_HANDLED();
}
case DOWN_SIG: {
if (me->timeout > 1) {
–me->timeout;
BSP_display(me->timeout);
}
return Q_HANDLED();
}
case ARM_SIG: {
return Q_TRAN(&Bomb4_timing); /
transition to “timing” /
}
}
return Q_IGNORED();
}
/
/
void Bomb4_timing(Bomb4 *me, QEvent const e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
me->code = 0; /
clear the defuse code */
return Q_HANDLED();
}
case UP_SIG: {
me->code <<= 1;
me->code |= 1;
return Q_HANDLED();
}
case DOWN_SIG: {
me->code <<= 1;
return Q_HANDLED();
}
case ARM_SIG: {
if (me->code == me->defuse) {
return Q_TRAN(&Bomb4_setting);
}
return Q_HANDLED();
}
case TICK_SIG: {
if (((TickEvt const )e)->fine_time == 0) {
–me->timeout;
BSP_display(me->timeout);
if (me->timeout == 0) {
BSP_boom(); /
destroy the bomb */
}
}
return Q_HANDLED();
}
}
return Q_IGNORED();
}

优点:

  • 采用面向对象的设计方法,很好的移植性
  • 实现了进入退出动作
  • 合适的粒度,且事件的粒度可控
  • 状态切换时通过改变指针,效率高
  • 可扩展成为层次状态机

缺点:

  • 对事件的定义以及事件粒度的控制是设计的最大难点,如串口接收到一帧数据,这些变量的更新单独作为某个事件,还是串口收到数据作为一个事件。再或者显示屏,如果使用此种编程方式,如何设计事件。

7. QP实现层次状态机

初始化层次状态机的实现:在初始化时,用户所选取的状态永远是最底层的状态,如上图,我们在计算器开机后,应该进入的是开始状态,这就涉及到一个问题,由最初top(顶状态)到begin 是有一条状态切换路径的,当我们设置状态为begin如何搜索这条路径成为关键(知道了路径才能正确的进入begin,要执行路径中过渡状态的进入和退出事件)。

void QHsm_init(QHsm *me, QEvent const *e)
{
Q_ALLEGE((me->state)(me, e) == Q_RET_TRAN);
t = (QStateHandler)&QHsm_top; /
HSM starts in the top state /
do { /
drill into the target… /
QStateHandler path[QEP_MAX_NEST_DEPTH_];
int8_t ip = (int8_t)0; /
transition entry path index /
path[0] = me->state; /
这里的状态为begin */

/通过执行空信号,从底层状态找到顶状态的路径/
(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
while (me->state != t) {
path[++ip] = me->state;
(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
}
/切换为begin/
me->state = path[0]; /* restore the target of the initial tran. /
/
钻到最底层的状态,执行路径中的所有进入事件 /
Q_ASSERT(ip < (int8_t)QEP_MAX_NEST_DEPTH_);
do { /
retrace the entry path in reverse (desired) order… /
QEP_ENTER_(path[ip]); /
enter path[ip] */
} while ((–ip) >= (int8_t)0);

t = path[0]; /* current state becomes the new source */
} while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN);
me->state = t;
}

状态切换:

//
QState result(Calc me, QEvent const e)
{
switch (e->sig)
{you
case ENTER_SIG:{
break;
}
case EXIT_SIG:{
break;
}
case C_SIG:
{
printf(“clear”);
return Q_HANDLED();
}
case B_SIG:
{
return Q_TRAN(&begin);
}
}
return Q_SUPER(&reday);
}
/
.ready为result和begin的超状态…
/
QState ready(Calc *me, QEvent const *e)
{
switch (e->sig)
{
case ENTER_SIG:{
break;
}
case EXIT_SIG:{
break;
}
case OPER_SIG:
{
return Q_TRAN(&opEntered);
}
}
return Q_SUPER(&on);
}

void QHsm_dispatch(QHsm *me, QEvent const *e)
{
QStateHandler path[QEP_MAX_NEST_DEPTH_];
QStateHandler s;
QStateHandler t;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
case OPER_SIG:
{
return Q_TRAN(&opEntered);
}
}
return Q_SUPER(&on);
}

void QHsm_dispatch(QHsm *me, QEvent const *e)
{
QStateHandler path[QEP_MAX_NEST_DEPTH_];
QStateHandler s;
QStateHandler t;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-USqIHwFe-1713544819400)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

#include #include #include "vxWorks.h" #include "msgQLib.h" #include "taskLib.h" /*#include "memPartLib.h"*/ #include "memLib.h" /*宏定义*/ #define MAX_MSGS (10) /* the length of msg*/ #define MAX_MSG_LEN sizeof(MESSAGE) /*the length of message*/ #define STACK_SIZE 20000 /*the stack size of task*/ #define DELAY_TICKS 50 /*the time of sending message*/ #define MAX_point 5 /*用户从系统内存池中获得内存的最大次数*/ #define size_1 30 /*用户分区的分配的大小*/ #define size_2 40 /*全局变量*/ int tidtask1; int tidtask2; int tidtask3; SEM_ID syncSemId; SEM_ID waitSemId; MSG_Q_ID myMsgQId1; MSG_Q_ID myMsgQId2; MSG_Q_ID myMsgQId3; typedef struct _MESSAGE { int mSendId; /*发送任务 ID*/ int mRecvId; /*接收任务 ID*/ int mData; /*消息中传递的数据*/ char Data[14]; } MESSAGE; /*内存管理*/ char* usermem1; char* usermem2; MESSAGE *point1[MAX_point]; MESSAGE *point2[MAX_point]; MESSAGE *point3[MAX_point]; int point1_index=0; int point2_index=0; int point3_index=0; PART_ID partid1; PART_ID partid2; #define MID_MESSAGE(id) (id) /*函数声明*/ int start(void); int task1(void); int task2(void); int task3(void); template T* mymalloc(unsigned nBytes); void myfree(void); void bye(void); /***************************************[progStart]*******************************************/ /*启动程序,创建息队例,任务*/ int start(void) { tidtask1=taskSpawn("tTask1", 220, 0, STACK_SIZE, (FUNCPTR)task1,0,0,0,0,0,0,0,0,0,0); usermem1=malloc(200); partid1=memPartCreate(usermem1,200); usermem2=malloc(400); partid2=memPartCreate(usermem2,400); return; } /**************************************[test_end]********************************************/ /*是否相等,相等返回1*/ int test_end(char *end,char *target) { int ret; if(!strcmp(end,target)) ret=1; else ret=0; return ret; } /****************************************[task1]***********************************************/ /*管理Task。负责系统启动时同步系统中其他Task的启动同步,利用信号量的semFlush()完成。同时接收各*/ /*Task的告警信息,告警信息需编号以logmsg方式输出。本task负责系统结束时的Task删除处理*/ int task1(void) { int singal; int message; MESSAGE *rxMsg=mymalloc(26); /*define messages,and alloc memory*/ memset(rxMsg,0,26); syncSemId=semBCreate(SEM_Q_FIFO,SEM_EMPTY); /*creat semaphore*/ waitSemId=semBCreate(SEM_Q_PRIORITY,SEM_EMPTY); myMsgQId1=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); /*create msgQ*/ myMsgQId2=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); myMsgQId3=msgQCreate(MAX_MSGS,MAX_MSG_LEN,MSG_Q_PRIORITY); tidtask2=taskSpawn("tTask2", 200, 0, STACK_SIZE, (FUNCPTR)task2,0,0,0,0,0,0,0,0,0,0); /*create task*/ tidtask3=taskSpawn("tTask3", 210, 0, STACK_SIZE, (FUNCPTR)task3,0,0,0,0,0,0,0,0,0,0); printf("Please input one of the following commands:add,sub,multiply,divide,testcommand\n"); /*the command we should put into the console*/ semFlush(syncSemId); /*release semaphore*/ semGive(waitSemId); while(1) { singal=1; msgQReceive(myMsgQId1,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER); if(rxMsg->mRecvId==MID_MESSAGE(3)) /*receive MsgQ from task3*/ { singal=test_end(rxMsg->Data,"wrong length")-1; logMsg("task3 receiveing a:%s\n",rxMsg->Data); /*put the warn from task3*/ logMsg("Please reput the other command!\n"); msgQReceive(myMsgQId1,(char*)&rxMsg,MAX_MSG_LEN,WAIT_FOREVER); /*recive MsgQ from task3*/ } if(rxMsg->mRecvId==MID_MESSAGE(2)) /*receive MsgQ from task2*/ { message=test_end(rxMsg->Data,"sysend"); if(message) { /*if the message from task2 is "sysend" and did not receive the warn from task3, close the system*/ if(singal) { bye(); } } else {/*if the message from task2 is "sysend" and receive the warn from task3, reput the command*/ if(singal) logMsg("task2 receiveing a %s\n",rxMsg->Data); logMsg("please reput the correct command!\n"); } } } return; } /********************************************************************************************/ int change_buf(char *command) { int ret; if(!strcmp(command,"add")) ret=1; else if(!strcmp(command,"sub")) ret=2; else if(!strcmp(command,"multiply")) ret=3; else if(!strcmp(command,"divide")) ret=4; else if(!strcmp(command,"testcommand")) ret=5; else ret=0; return ret; } /****************************************[task2]*********************************************/ /*console 命令行接收Task。接收并分析console发来的命令行及参数。自行设置5种以上命令,并根据命*/ /*令的内容向Task3发送激励消息。同时实现系统退出命令,使系统采用适当方式安全退出。收到非法命令*/ /*向Task1告警*/ int task2(void) { char buf[100]; int command; char *str=mymalloc(35); MESSAGE *txMsg=mymalloc(26); memset(str,0,35); memset(txMsg,0,26); txMsg->mSendId=MID_MESSAGE(2); txMsg->mRecvId=MID_MESSAGE(2); FOREVER { semTake(syncSemId,WAIT_FOREVER); semTake(waitSemId,WAIT_FOREVER); gets(buf); command=change_buf(buf);/*change the commands into numbers*/ switch(command) { case 0:/*receive uncorrect command*/ txMsg->mData=0; strcpy(txMsg->Data,"wrong command");/*send warn to task1*/ msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); break; case 1:/*receive add command*/ strcpy(str,"This an add caculate!\0"); txMsg->mData=1; break; case 2:/*receive sub command*/ strcpy(str,"This a sub caculate!\0"); txMsg->mData=2; break; case 3:/*receive multiply command*/ strcpy(str,"This a multiply caculate!\0"); txMsg->mData=3; break; case 4:/*receive divide command*/ strcpy(str,"This a divide caculate!\0"); txMsg->mData=4; break; case 5:/*receive testcommand,send a long string to task3*/ strcpy(str,"This a testcommand to warn task1!\0"); txMsg->mData=5; break; default: break; } if(txMsg->mData!=0) {/*send along string to task3,and send a message to taks3*/ msgQSend(myMsgQId3,(char*)&str,sizeof(str),WAIT_FOREVER,MSG_PRI_NORMAL); msgQSend(myMsgQId3,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); } semGive(waitSemId); semGive(syncSemId); taskDelay(DELAY_TICKS); if(txMsg->mData!=0) {/*send sysend to task1 to let task1 close system*/ strcpy(txMsg->Data,"sysend"); msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); } } return; } /****************************************[task3]********************************************/ /*console输出Task。接收需打印输出的字串消息(命令),输出到console。收到长度为0或超常字串向*/ /*Task1告警*/ int task3(void) { int firstData=100; int secondData=10; MESSAGE *rxMsg=mymalloc(26); MESSAGE *txMsg=mymalloc(26); char *rstr=mymalloc(35); memset(txMsg,0,26); memset(txMsg,0,26); memset(rstr,0,35); txMsg->mSendId=MID_MESSAGE(3); txMsg->mRecvId=MID_MESSAGE(3); while(1) { semTake(syncSemId,WAIT_FOREVER); msgQReceive(myMsgQId3,(char*)&rstr,sizeof(rstr),WAIT_FOREVER); if(strlen(rstr)=26) {/*make sure whether the string is too long or short*/ strcpy(txMsg->Data,"wrong length"); msgQSend(myMsgQId1,(char*)&txMsg,sizeof(txMsg),WAIT_FOREVER,MSG_PRI_NORMAL); /*msgQReceive(myMsgQId3,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER);*/ } semTake(waitSemId,WAIT_FOREVER); msgQReceive(myMsgQId3,(char*)&rxMsg,sizeof(rxMsg),WAIT_FOREVER); if(rxMsg->mData!=5) {/*when it is not testcommand,printf these*/ printf("%s\n",rstr); printf("there are two datas!\n"); printf("firstData:100\n"); printf("secondData:10\n"); } switch(rxMsg->mData) { case 1:/*printf add caculate*/ printf("The result is:%d\n",firstData+secondData); break; case 2:/*printf sub caculate*/ printf("The result is:%d\n",firstData-secondData); break; case 3:/*printf multiply caculate*/ printf("The result is:%d\n",firstData*secondData); break; case 4:/*printf divide caculate*/ printf("The result is:%d\n",firstData/secondData); break; case 5: break; default: break; } semGive(waitSemId); semGive(syncSemId); taskDelay(DELAY_TICKS); } return; } template T* mymalloc(unsigned nBytes) { T* point; int i=0; /*用户分区一是否能分配的标志位*/ int j=0; /*用户分区二是否能分配的标志位*/ if(nBytes=size_1 && nBytes=size_2) && point3_index<MAX_point) /*若用户分区二不能分配,由系统内存池来分配,且只能从系统内存池中分配MAX_point次*/ { point=malloc(nBytes); point3[point3_index]=point; printf("the number of the point3_index is:%d\n",point3_index); point3_index++; } return point; } void myfree(void) { int i=0; for (i=0;i<point1_index;i++) { memPartFree(partid1,point1[i]); } for (i=0;i<point2_index;i++) { memPartFree(partid2,point2[i]); } for (i=0;i<point3_index;i++) { free(point3[i]); } free(usermem1); free(usermem2); printf("The memory have freed!\n"); } void bye(void) { myfree(); logMsg("Bye-bye\n"); taskDelete(tidtask2); taskDelete(tidtask3); msgQDelete(myMsgQId1); msgQDelete(myMsgQId2); msgQDelete(myMsgQId3); semDelete(syncSemId); taskDelete(tidtask1); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值