【Callback接口初探】工程化编程实战Callback接口学习笔记
工程化编程实战Callback接口学习笔记
环境:
- 系统:Manjaro
- 内核版本:5.4.24
- gcc 版本:9.2.1
- VS Code:1.43.0
任务:
- 在VS Code下编译运行lab5-1.tar.gz
- 通过VS Code + GDB调试程序找出quit命令无法运行的bug产生的原因
- 分析Callback接口的运行机制,总结Callback接口设计的方法
编译项目
-
VS Code下进行编译。
Terminal>Run Build Task
,新建tasks.json
文件 -
因为要进行链接,所以将其中的
args
改为如下,其他具体VS Code操作见上一篇博客"args": [ "-g", // 编译的时候需要带-g参数,否则无法进入调试 "${fileDirname}/linktable.c", // 这里,在menu.c前,先编译linktable.c "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}" // 输出文件和源代码同名,不带后缀 ],
-
或者直接使用gcc进行编译
$ gcc linktable.c menu.c -o menu
发现错误
menu.c:49:8: 警告:隐式声明函数‘strcmp’
这是没有包含进string.h
导致的 -
在
menu.c
文件内加入#include <string.h>
重新编译
执行项目
-
终端输入
./menu
执行程序$ ./menu Input a cmd number > help help - Menu List: help - Menu List: version - Menu Program V1.0 quit - Quit from Menu Program V1.0 Input a cmd number > quit This is a wrong cmd!
发现程序可以运行,但存在bug,无法识别
quit
命令。接下来进入调试,跟踪程序执行情况。
准备调试环境
-
首先安装配置好VS Code,打开项目,过菜单
Run>Add Configuration
,选择C++ (GDB/LLDB)>g++ build and debug active file
来创建VS Code调试用的launch.json
文件。更多操作见上一篇博客launch.json
大致如下:{ // launch.json "version": "0.2.0", "configurations": [ { "name": "gcc build and debug active file", "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "gcc build active file", "miDebuggerPath": "/usr/bin/gdb" } ] }
配置文件中的
stopAtEntry
也可以设置为true
,这样程序进入调试时,可以自动在main函数设置断点。
调试修复
-
回到
menu.c
文件,在scanf("%s", cmd);
后,也就是输入命令后设置断点。 -
F5
进入调试 -
将
cmd
添加到WATCH
-
先输入help,执行
可以看到,cmd的内容是字符串
help
,且p指针指向一个tDataNode
,通过desc
、handler
可以看出正确匹配命令,所以输出正常输入
quit
后,cmd
正常,但指针p指向的是空结点,所以问题可能出在FindCmd
函数内(也可能是命令列表初始化时的问题)。// FindCmd tDataNode* FindCmd(tLinkTable * head, char * cmd) { return (tDataNode*)SearchLinkTableNode(head,SearchCondition); }
FindCmd
函数通过指针函数SearchCondition
作为条件,调用linkTable
内的SearchLinkTableNode
。 -
对
SearchCondition
添加断点发现,在进入两次
SearchCondition
后,就结束了搜索,前两次分别如下但linkTable内,在
InitMenuData
函数中,确实插入了三个结点,调试时也可以看到。由只进入两次条件函数,可以断定问题出在
SearchLinkTableNode
的边界判定上。// SearchLinkTableNode while(pNode != pLinkTable->pTail) { if(Conditon(pNode) == SUCCESS) { return pNode; } pNode = pNode->pNext; }
SearchLinkTableNode
的结束条件判断为pNode != pLinkTable->pTail
,但pLinkTable->pTail
最后一个结点恰好指向命令列表中的最后一个结点quit
,所以循环到最后结点时,无法成功进入。 -
将循环判断条件改为
pNode != NULL
程序即可正常运行
callback接口的运行机制
C语言里的Callback机制,靠将函数指针传递到另一函数中,然后在需要触发某个事件的时候调用该方法。这种实现方式简单且灵活,是面向过程的。
在需要回调的函数中先预留一个函数指针参数,接着在外部定义该具体的被回调函数,在执行时将被回调函数作为指针传入,在回调函数中满足某特定条件后,调用该函数。
回调机制能减少不同模块之间的耦合程度,简化了用户接口。
作者:SA19225176,万有引力丶
参考资料来源:USTC SE2020高级软件工程