文章目录
一. 多进程的调试
1. 使用vscode的gdb attach调试
调试注意
-
- 调试时报错解决方法click here
-
- 多进程调试时,要尽量在程序需要停止的地方加上getchar(),同时打上断点,双保险保证程序能够在指定的地方停止。
-
- 擅用fflush和getch()
-
- 使用fflush(stdout)
printf函数是按行刷新的缓冲区的,如果printf()中不加\n,会导致不能立即输出的情况,而是需要等到程序结束自动,清空缓冲区时才能输出。此时我们可以使用fflush(stdout)提前清空缓冲区。
- 使用fflush(stdout)
-
- 使用fflush(stdin)
使用fflush(stdin)放到getchar(),scanf()等输入函数后面,来避免输入缓冲区异常
- 使用fflush(stdin)
-
- 擅用fflush和getch()
-
二. 对管道文件的操作
1. 查看管道文件的状态
管道文件虽然不能直接查看大小,但是我们可以通过lsof | grep 文件名,来查看管道文件是否打开 (但注意管道文件打开不等于就能够进行通讯,看第二点),以及被以什么方式打开(读或写)。
2.write,read函数和管道文件
管道文件能够通信的条件是,管道打开,且write和read函数在管道两端同时监听,即是write在写入管道的同时管道另一端必须要正在read。
wirte返回值为0时原因是,(1)read没有正在监听(2)写入数据为0字节
read返回值为0时原因是,(1)write没有正在监听没有监听的原因可能是(1)有getchar()等函数卡住了进程导致write和read函数没有执行。(2)服务器或客户端挂掉(3)也可能是管道异常关闭。
对于管道异常关闭我们使用,以下代码检测
sigpipe_handler函数模板
void sigpipe_handler(int signum) {
printf ("SIGPIPE received\n");
}
sigpipe_handler的目的是当管道出现关闭,signal会捕获到SIGPIPE,信号,注册
sigpipe_handler函数并执行。下面的判断语句是检测是否成功注册sigpipe_handler函数
if (signal(SIGPIPE, sigpipe_handler) == SIG_ERR) {
std::cerr << "Failed to register SIGPIPE handler\n";
return 1;
}
因为一个管道可以有多个读端和写端,所以必须要管道另一端确实没有任何读入 端时,write才会返回0。同时signal函数接受到pipe broken信号
反之同理需要确实没有任何输入端,read才会返回0。同时signal函数接受到pipe broken信号open管道的条件是读端和写端被同时打开,如果只是打开open一端,则会卡在open函数无法继续,必须两端都被打开open函数能执行
三. 犯错
犯错:
又是括号惹的祸,记得之前写嵌入式小车时因为if括号问题导致优先级错误所以一直卡住。这次的猜数游戏也是if括号的问题导致优先级错误。
(重点) 四. 如何调试fork产生的父子进程
使用gdb attach加kill挂起/开始进程,可以调试父子进程
问题1:在从1进程切换到2进程时,如果我们在1进程后的某一个位置打断点,试图想当我们调试2进程时让1进程停到断点这是不可行的,当切换进程后进程1会自动执行而不会在断点停下。
解决方法:当我们在切换进程之前先使用kill -STOP <进程号>挂起当前进程,然后切换到另一进程调试。调试完后用同样的方法回到当前进程,然后使用kill -CONT <进程号>开始当前进程但注意此时要在后面某个位置打断点,以便开始进程后能够在断点处停下来。同时,当我们切回进程的时候程序会报错signal STOP这是因为我们发出的暂停信号,当启动时又会报错signal continue,这是因为我们发出的开始信号,但这不影响程序执行
五. 重启进程
1. 我们重启进程可以使用fork() + exec()
(1)当路径为可执行文件时
void restart1(){
printf("try restarting\n");
pid_t pid = fork();
if(pid == 0){
char self_path[1024];
char *argv[] = {"/home/guoziyi/Desktop/ALL/guess/build/client1", NULL};
printf("current exe path is: %s\n", argv[0]);
if(execv(argv[0], argv) == -1){
perror("evecv error");
exit(1);
}
printf("restart successfully\n");
}
}
(2)当路径为可执行文件的链接时我们使用readlink,注意reaklink返回值为真实路径的长度
void restart(){
printf("try restarting\n");
pid_t pid = fork();
if(pid == 0){
char *argv[] = {"/home/guoziyi/Desktop/ALL/guess/build/client1", NULL};
path_length = readlink("/home/guoziyi/Desktop/ALL/guess/build/client1", self_path, sizeof(self_path) - 1);
self_path[path_length] = '\0';
printf("current exe path is: %s\n", self_path);
if(execv(self_path, argv) == -1){
perror("evecv error");
exit(1);
}
printf("restart successfully");
}
}
(3)在重启进程,并在新的终端打开
void restart1(){
printf("try restarting\n");
pid_t pid = fork();
if(pid == 0){
char self_path[1024];
char *argv[] = {"/usr/bin/gnome-terminal", "--", "/home/guoziyi/Desktop/ALL/guess/build/client1", NULL};
printf("current exe path is: %s\n", argv[2]);
if(execv(argv[0], argv) == -1){
perror("evecv error");
exit(1);
}
printf("restart successfully\n");
}
}
2.为什么execv(self_path, argv) 的第二个参数必须至少有一个,即argv[0]为什么不能为空?
当我们打开一个进程的时候时候,肯定会从main函数开始执行。我们知道
int main(int argc, int *argv[]),中的argv[0]默认传递的就是可执行文件的路径,所以我们调用execv(self_path, argv)时,至少argv必须有一个元素,且为可执行文件的路径
五. 使用vscode和gdb联调
在启动vscode调试之后,可以再在调试控制台使用gdb命令辅助调试。
- 查看所用栈帧
-exec bt - 查看指定栈帧
-exec f 栈帧号 - 打印变量类型和大小
-exec ptype 变量名
参考
六. 如何获取调试失败的信息
我们可以通过调试core文件,来找到失败原因
参考
七. VS,clion调试
在对Windows应用程序附加到进程调试最好使用vs
参考
八. vscode找不到头文件或c++库
1.查看库文件目录
Windows:一般使用mingw_x64,所以在mingw_x64目录进行寻找
linux:一般使用gcc和g++,使用 **echo | g++ -v -x c++ -E -**来查看库文件目录
2.导入库文件目录
(1)我们可以按照提示配置为在库文件所在文件夹下递归搜索
(2)在vscode中我们按照下“Shift+Ctrl+p” 输入: C++Configurations,选择UI界面或使用c_cpp_properties.json文件导入头文件目录即可
(3)如果还不行,就禁用重启所有的c/c++扩展