1. GDB调试方法 & 程序突然卡顿的调试步骤
问题:如何用GDB调试?如果程序突然卡一下,如何定位问题?
回答:
-
GDB基本调试流程:
- 编译时添加
-g
选项保留调试信息:gcc -g program.c -o program
- 启动GDB:
gdb ./program
- 设置断点:
break <函数名/行号>
- 运行程序:
run
- 单步执行:
next
(不进入函数)或step
(进入函数) - 查看变量值:
print <变量名>
- 查看堆栈:
backtrace
- 编译时添加
-
程序卡顿调试步骤:
- 捕获卡顿现场:用
gdb -p <PID>
附加到进程,执行backtrace
查看当前堆栈。 - 检查线程状态:
info threads
查看所有线程状态,切换到卡住的线程(thread <id>
),分析其堆栈。 - 死锁检查:若卡在锁操作(如
pthread_mutex_lock
),检查是否有未释放的锁或循环等待。 - 系统调用阻塞:用
strace -p <PID>
跟踪系统调用,观察是否卡在read
/write
等IO操作。 - 条件断点:设置断点触发条件(如变量值突变时暂停),例如:
break if var==100
。
- 捕获卡顿现场:用
2. 线程池实现思路 & 任务队列空时如何唤醒
问题:实现线程池的思路?任务队列为空时如何唤醒线程?
回答:
-
线程池实现思路:
- 任务队列:使用队列(
std::queue
或链表)存储待处理任务,通过互斥锁(pthread_mutex_t
)保护队列。 - 线程管理:预先创建固定数量的线程(
pthread_create
),线程循环从任务队列取任务执行。 - 条件变量:当队列为空时,线程通过条件变量(
pthread_cond_wait
)进入等待状态;有新任务时通过pthread_cond_signal
唤醒。
- 任务队列:使用队列(
-
任务队列空时的唤醒:
- 使用条件变量
pthread_cond_t
实现阻塞等待。 - 当任务队列为空时,线程调用
pthread_cond_wait(&cond, &mutex)
释放锁并进入等待。 - 唤醒方式:添加新任务时调用
pthread_cond_signal
(唤醒一个线程)或pthread_cond_broadcast
(唤醒所有线程)。
- 使用条件变量
3. 数组退化为指针问题 & sizeof结果
问题:数组退化为指针是什么?求 sizeof
的结果?
回答:
- 数组退化为指针:当数组作为函数参数传递时,会退化为指向首元素的指针,丢失长度信息。
- 示例:
void func(char arr[]) { // 此处 arr 退化为指针,sizeof(arr) = 4或8(指针大小) } int main() { char arr[100]; // sizeof(arr) = 100(数组总大小) func(arr); }
- 结论:函数内对数组参数使用
sizeof
会得到指针大小,而非数组实际大小。
4. 指针传递导致崩溃问题
问题:函数传递指针但未使用引用,内部 malloc
导致崩溃,如何解决?
回答:
- 问题代码:
void func(int* ptr) { ptr = (int*)malloc(sizeof(int)); // 修改的是局部指针副本 } int main() { int* p = nullptr; func(p); // p仍为nullptr,后续访问崩溃 }
- 修正方法:
- 使用二级指针:
void func(int** ptr) { *ptr = malloc(...); }
- 使用引用(C++):
void func(int*& ptr) { ptr = new int; }
- 使用二级指针:
5. C++向上转型与向下转型
问题:C++类型转换中,向上转型和向下转型的具体含义?
回答:
- 向上转型(Upcasting):将派生类指针/引用转换为基类指针/引用,是安全的隐式转换。
Derived d; Base* b = &d; // 向上转型
- 向下转型(Downcasting):将基类指针/引用转换为派生类指针/引用,需显式转换(
dynamic_cast
),需基类有虚函数(RTTI支持)。Base* b = new Derived; Derived* d = dynamic_cast<Derived*>(b); // 向下转型(需检查是否成功)
6. 类依赖优化(A.h与B.h)
问题:B类中包含 A a;
,如何优化头文件依赖?
回答:
- 问题:直接包含
A.h
会导致编译依赖,若A.h
修改则B.h
需重新编译。 - 优化方法:
- 前置声明:在
B.h
中声明class A;
,避免直接包含A.h
。 - 改用指针:将
A a
改为A* a
,在B.cpp
中包含A.h
并管理对象生命周期。
- 前置声明:在
7. 类声明注意事项
问题:类声明时需要注意什么?
回答:
- 头文件保护宏:防止重复包含。
#pragma once // 或 #ifndef CLASS_H #define CLASS_H ... #endif
- 成员访问控制:明确
public
/protected
/private` 作用域。 - 构造/析构函数:若需要动态资源管理,需定义拷贝构造、赋值运算符(或
=delete
)。 - 虚函数设计:若类可能被继承,析构函数应声明为虚函数(
virtual ~Base() {}
)。 - 避免暴露实现细节:尽量将数据成员设为
private
,通过接口访问。
总结:面试重点考察C++底层知识、多线程调试经验及代码设计能力,需熟练掌握GDB、内存管理、设计模式等核心技能。