1、编译运行相关程序
1.1、Ubuntu和windows下的vs Code工具,编译工具g++,实验代码lab5-1.tar.gz 即http://pan.baidu.com/s/1pJ0qAIv
1.2、使用gcc编译程序linktable.c和menu.c,即使用命令gcc -o test linktable.c menu.c,将linktable和menu.c编译成可执行文件,名为test。
注意需要在menu.c的头文件中加入#include
1.3、输入./test运行之前编译的程序,然后在程序中输入help查看程序提供的命令参数,但是发现输入quit时,程序没有识别出该命令。
附windows下通过vs Code的运行情况(提前配置好pthreads):
2、问题发现和程序修改
2.1、使用GDB断点调试gcc -g linktable.c menu.c -o menu gdb menu,可以首先定位到menu.c的主函数main中,发现当tDataNode 类型的指针p为NULL时,报出This is a wrong cmd!信息,而指针p的值来源于函数FindCmd函数,观察FindCmd程序,可以继续定位到linktable.c文件中的SearchLinkTableNode函数
int main() { InitMenuData(&head); /* cmd line begins */ while(1) { printf("Input a cmd number > "); scanf("%s", cmd); tDataNode *p = FindCmd(head, cmd); if( p == NULL) { printf("This is a wrong cmd!\n "); continue; } printf("%s - %s\n", p->cmd, p->desc); if(p->handler != NULL) { p->handler(); } } }
tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition); }
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode)) { if(pLinkTable == NULL || Conditon == NULL) { return NULL; } tLinkTableNode * pNode = pLinkTable->pHead; while(pNode != pLinkTable->pTail) { if(Conditon(pNode) == SUCCESS) { return pNode; } pNode = pNode->pNext; } return NULL; }
该函数的两个参数分别表示由命令参数组成的链表头指针和当前命令是否找到,以下情况返回NULL:
pLinkTable == NULL
Conditon == NULL
该链表中所有节点的Condition都不为SUCCESS
调试程序,发现只有当pNode != pLinkTable->pTail条件为真时,不断的遍历链表上的节点信息,来对比用户输入的命令从而返回相应节点。所以继续定位到该循环,发现循环条件比较奇怪,我们继续调试这个条件,观察链表的结构和链表的添加节点操作。
tLinkTable * CreateLinkTable() { tLinkTable * pLinkTable = (tLinkTable *)malloc(sizeof(tLinkTable)); if(pLinkTable == NULL) { return NULL; } pLinkTable->pHead = NULL; pLinkTable->pTail = NULL; pLinkTable->SumOfNode = 0; pthread_mutex_init(&(pLinkTable->mutex), NULL); return pLinkTable; }
int AddLinkTableNode(tLinkTable *pLinkTable,tLinkTableNode * pNode) { if(pLinkTable == NULL || pNode == NULL) { return FAILURE; } pNode->pNext = NULL; pthread_mutex_lock(&(pLinkTable->mutex)); if(pLinkTable->pHead == NULL) { pLinkTable->pHead = pNode; } if(pLinkTable->pTail == NULL) { pLinkTable->pTail = pNode; } else { pLinkTable->pTail->pNext = pNode; pLinkTable->pTail = pNode; } pLinkTable->SumOfNode += 1 ; pthread_mutex_unlock(&(pLinkTable->mutex)); return SUCCESS; }
分析上面的代码可以发现pLinkTable->pTail代表的是链表的最后一个节点的指针,这下在回到SearchLinkTableNode函数中的循环,发现当链表到达最后节点是就不再循环了,即并没有判断最后一个节点的Condition,中途停止返回空。
2.2、修改程序消除bug
知道了指针返回为空的原因是没有彻底遍历链表中的所以节点,漏了最后一个节点,所以我们修改循环条件
pNode != pLinkTable->pTail为pNode != pLinkTable->pTail-pNext或pNode != NULL
修改后重新编译运行测试成功
windows下:
linux下:
3、callback接口总结
系统API(通常都是)和回调函数,这两者缺一不可。其实回调的基本思想就是由系统给我们提供一些接口,也就是常使用的API,这种函数可以将某个其他函数的地址作为其参数之一,而且可以利用该地址对这个函数进行调用,而被调用的函数就是我们通常所说的回调函数了。
例如该实验中在SearchLinkTableNode函数中,Conditon(tLinkTableNode * pNode)调用了回调函数
说明:
回调函数并不由开发者直接调用执行(只是使用系统接口API函数作为起点)
回调函数通常作为参数传递给系统API,由该API来调用,回调函数可能被系统API调用一次,也可能被循环调用多次(SortItem就是自调用)
使用此函数可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特定原型和限制条件的被调用函数