当前位置: 首页 > news >正文

C++编译过程与GDB调试段错误和死锁问题

在 C++ 开发过程中,了解编译流程和掌握调试技巧是提升开发效率的关键。本文将详细解析 C++ 的编译流程,介绍 g++ 编译命令的使用,并深入讲解 GDB 调试工具的常用技巧,包括段错误和死锁的调试方法。​

C++ 编译流程​

C++ 代码从源代码到可执行文件需要经历四个主要阶段:预处理、编译、汇编和链接。每个阶段都有其特定的功能和可能出现的错误。​

我们以一个最简单的示例代码做演示

预处理阶段​

预处理是编译的第一步,主要处理以#开头的预处理指令。使用g++ -E main.cpp > main.e命令可以生成预处理后的文件 main.e。​

预处理阶段的主要工作包括:​

  • 宏定义替换:将所有#define定义的宏进行文本替换,例如#define MAX 100会将代码中所有 MAX 替换为 100​
  • 处理头文件包含:将#include指令指定的头文件内容递归插入到当前文件中​
  • 处理条件编译:如#if、#ifdef、#elif、#else、#endif等,根据条件保留或删除部分代码​
  • 删除注释:将代码中的注释替换为空格​
  • 添加行号和文件名标识:便于后续编译阶段生成错误信息​

main.cpp:

#define MAX 100#if 1int main()
{int a = MAX;return 0;
}#endif

main.e:

# 0 "main.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "main.cpp"int main()
{int a = 100;return 0;
}

预处理阶段可能出现的错误:​

  • 宏定义语法错误,如缺少括号、引号不匹配​
  • 头文件找不到(fatal error: xxx.h: No such file or directory)​
  • 嵌套包含导致的文件路径错误​(#pragma once 或 #ifndef HEADFILE_H解决)

编译阶段​

编译阶段将预处理后的代码(.i 或.e 文件)转换为汇编代码(.s 文件),使用g++ -S main.cpp命令可生成汇编文件 main.s。​

编译过程主要包括:​

  • 词法分析:将源代码分解为单词(token),如关键字、标识符、常量等​
  • 语法分析:根据语法规则将单词组成语法树,检查语法错误​
  • 语义分析:检查语法树的语义是否正确,如类型匹配、变量未定义等​
  • 中间代码生成:将语法树转换为中间代码(如三地址码)​
  • 代码优化:对中间代码进行优化,提高执行效率​
  • 目标代码生成:将优化后的中间代码转换为汇编指令​

main.s: 

	.arch armv8-a.file	"main.cpp".text.align	2.global	main.type	main, %function
main:
.LFB0:.cfi_startprocsub	sp, sp, #16.cfi_def_cfa_offset 16mov	w0, 100str	w0, [sp, 12]mov	w0, 0add	sp, sp, 16.cfi_def_cfa_offset 0ret.cfi_endproc
.LFE0:.size	main, .-main.ident	"GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0".section	.note.GNU-stack,"",@progbits

编译阶段常见错误:​

  • 语法错误:如缺少分号、括号不匹配​
  • 类型错误:如不同类型变量赋值​
  • 未声明的变量或函数使用​

汇编阶段​

汇编阶段将汇编代码(.s 文件)转换为机器码(二进制指令),生成目标文件(.o 或.obj 文件)。使用g++ -c main.cpp命令可生成目标文件 main.o。​

汇编过程相对简单,主要是将每条汇编指令对应到特定 CPU 的机器码,不进行复杂的语法检查,只检查汇编指令的有效性。​

链接阶段​

链接阶段将多个目标文件(.o)和所需的库文件组合在一起,生成可执行文件。​

链接主要完成:​

  • 符号解析:将目标文件中引用的外部符号(如函数、全局变量)与定义这些符号的目标文件关联起来​
  • 重定位:调整目标文件中符号的地址,使其在最终的可执行文件中具有正确的内存地址​

链接阶段常见错误:​

  • 未定义引用(undefined reference):引用了未实现的函数或变量​
  • 多重定义(multiple definition):同一个符号在多个目标文件中被定义​
  • 库文件找不到:指定的库文件不存在或路径错误​

GDB 调试​

GDB(GNU Debugger)是一款功能强大的命令行调试工具,支持断点设置、变量查看、程序单步执行等功能。使用g++ -g main.cpp -o main命令编译程序时,-g选项会在可执行文件中加入调试信息,使 GDB 能够进行调试。​

常用 GDB 命令​

1. bt:查看当前线程的调用栈(backtrace),显示函数调用关系和参数​

2. list <行号>|< 函数名 >:查看指定行号或函数的源代码​

(gdb) list 10 # 查看第10行附近的代码​(gdb) list main # 查看main函数的代码​

3. break:设置断点​

(gdb) break 20 # 在第20行设置断点​(gdb) break func_name # 在函数func_name处设置断点​(gdb) break file.cpp:30 # 在file.cpp的第30行设置断点​

​4. info break:查看所有断点信息,包括断点编号、位置、状态等​

5. run:开始执行程序,若有断点则在断点处暂停​

6. print:打印变量或表达式的值​

(gdb) print x # 打印变量x的值​(gdb) print a + b # 打印表达式a + b的值​(gdb) print array[0] # 打印数组第一个元素​

7. next:执行下一行代码,不进入函数调用(单步执行)​

8. step:执行下一行代码,若有函数调用则进入函数内部(单步跟踪)​

9. continue:继续执行程序,直到遇到下一个断点或程序结束​

GDB调试段错误(Core Dump)

段错误(Segmentation Fault)通常是由于程序访问了非法内存地址导致的。通过 Core Dump 文件可以记录程序崩溃时的内存状态,便于事后调试。​

段错误实例代码(segmenterr.cpp):

#include <iostream>int main()
{int* a = 0;*a = 10;return 0;
}//g++ -g -o segmenterr segmenterr.cpp

(1)开启 Core Dump:​

默认情况下,系统可能限制 Core 文件的生成,使用ulimit -a命令可以查看系统资源限制。其中core file size表示 Core 文件的大小限制。​

ulimit -c unlimited # 设置Core文件大小不受限制​

 

​(2)生成 Core 文件:​

当程序发生段错误时,会自动生成名为core或core.PID的文件,ubuntu系统下,默认生成在执行文件同目录下,可以通过修改/etc/sysctl.conf配置core文件生成路径:

修改/etc/sysctl.conf,再最后插入kernel.core_pattern=<路径>,例如:

kernel.core_pattern=/home/orangepi/core_dump/core-%e-%p-%t

 core_dump_core-%e-%p-%t时期望生成core文件的文件名模板,其中:

%e: 执行文件名称

%p: <pid>

%t: dump的时间

 创建对应文件夹(我这里是core_dump文件夹)

mkdir /home/orangepi/core_dump

执行:

sudo sysctl -p /etc/sysctl.conf

使用cat查看配置路径是否生效:

cat /proc/sys/kernel/core_pattern

 (3)使用 GDB 调试 Core 文件:​

先执行程序,时段错误发生并产生core

执行 

gdb <可执行文件> <core文件路径>
我的是:
gdb ./segmenterr /home/orangepi/core_dump/core-segmenterr-9214-1754215410

(gdb) bt # 查看崩溃时的调用栈,定位错误位置​

死锁调试​

死锁是多线程程序中常见的问题,当两个或多个线程互相等待对方释放资源时就会发生死锁。​

死锁示例代码​

#include <pthread.h>
#include <iostream>using namespace std;pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;void *thread1(void *arg) {pthread_mutex_lock(&mutex1);cout << "Thread 1 locked mutex1" << endl;// 模拟一些工作sleep(1);pthread_mutex_lock(&mutex2);  // 等待mutex2cout << "Thread 1 locked mutex2" << endl;pthread_mutex_unlock(&mutex2);pthread_mutex_unlock(&mutex1);return NULL;
}void *thread2(void *arg) {pthread_mutex_lock(&mutex2);cout << "Thread 2 locked mutex2" << endl;// 模拟一些工作sleep(1);pthread_mutex_lock(&mutex1);  // 等待mutex1cout << "Thread 2 locked mutex1" << endl;pthread_mutex_unlock(&mutex1);pthread_mutex_unlock(&mutex2);return NULL;
}int main() {pthread_t t1, t2;pthread_create(&t1, NULL, thread1, NULL);pthread_create(&t2, NULL, thread2, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_mutex_destroy(&mutex1);pthread_mutex_destroy(&mutex2);return 0;
}

GDB 调试死锁方法​

1.编译带调试信息的程序:​

g++ -g -o lockerr lockerr.cpp -lpthread

 2.运行程序并使用 GDB 附加进程:​

gdb attach <pid> # 附加到进程

gdb attach 9906

3. 查看线程信息:​

(gdb) info threads # 显示所有线程​(gdb) thread 2 # 切换到线程2​(gdb) bt # 查看线程2的调用栈​(gdb) thread 3 # 切换到线程3​(gdb) bt # 查看线程3的调用栈​

在 GDB 中可以使用info threads查看所有线程的状态,结合bt命令查看线程的调用栈,分析线程正在等待的锁资源,从而定位死锁原因。​

(gdb) thread apply all bt # 查看所有线程的调用栈​

#1 pthread_mutex_lock  

#2 thread2 (arg=0x0) at lockerr.cpp:31
说明 线程 3(对应thread2函数)在 lockerr.cpp 的第 31 行调用 pthread_mutex_lock,尝试获取某个互斥锁,但陷入阻塞

#1 pthread_mutex_lock  

#2 thread1 (arg=0x0) at lockerr.cpp:16
说明 线程 2(对应thread1函数)在 lockerr.cpp 的第 16 行调用 pthread_mutex_lock,尝试获取某个互斥锁,也陷入阻塞

通过以上方法,可以清晰地看到两个线程阻塞位置,从而确认死锁的发生位置和原因。​

http://www.dtcms.com/a/312405.html

相关文章:

  • 北邮:LLM强化学习架构Graph-R1
  • C++-二叉树OJ题
  • 【反转字符串中的单词】
  • 从零开始设计一个分布式KV存储:基于Raft的协程化实现
  • 吴恩达【prompt提示词工程】学习笔记
  • C# async await 实现机制详解
  • GR-3:字节跳动推出40亿参数通用机器人大模型,精确操作提升250%,开启具身智能新纪元!
  • FasrCGI
  • ospf笔记和 综合实验册
  • visual studio code 怎样将主题修改成亮色,并且配置中文界面
  • zookeeper常见命令和常见应用
  • MySQL——运维篇
  • K8S部署ELK(五):集成Kibana实现日志可视化
  • MySQL面试题及详细答案 155道(021-040)
  • 使用Database Navigator插件进行连接sqlite报错invalid or incomplete database
  • 2025年开关电源行业深度解析:从传统应用到新兴赛道的黄金赛道
  • MVC 发布
  • 代码随想录day53图论4
  • trace-cmd记录线程被中断打断的时间
  • 笔试——Day27
  • RabbitMQ面试精讲 Day 10:消息追踪与幂等性保证
  • spring-ai-alibaba 之 graph 槽点
  • 【设计模式】4.装饰器模式
  • 2025-0803学习记录21——地表分类产品的精度验证
  • Github怎么只下载某个目录文件?(Git稀疏检出、GitZip for Github插件、在线工具DownGit)Github下载目录
  • linux2.6 和 unix-v6 源码实验
  • Nginx相关实验(2)
  • 【2025/08/03】GitHub 今日热门项目
  • C# LINQ(LINQ to XML)
  • CAP 理论笔记