1. Windows 和 Linux 解决崩溃

Windows:程序崩溃 → 生成 .dmp → WinDbg 打开 .dmp → 加载 exe/dll + PDB → !analyze -v → 看异常线程 → 看调用栈 → 看寄存器/异常地址 → 定位源码

Pdb 全称 Program database,是调试符号文件。pe 文件 和 pdb 文件有一套匹配信息。

Address Space Layout Randomization,地址空间布局随机化。

LInux:core dump:程序崩溃那一瞬间的内存快照和 CPU 现场。 debug symbols 类似于 pdb

一些gdb 命令:bt 调用栈, frame 栈帧

  1. 野指针、悬空指针和内存泄漏

野指针:指针没有被正确初始化,指向未知地址

悬空指针:指针原来有效,但所指对象已经被释放/销毁

内存泄漏:申请的内存没有释放,同时又失去了访问它的方式

  1. 处理死锁问题

先确认是不是死锁,然后通过线程栈、core 或数据库 deadlock 日志找到锁等待关系;接着看是不是锁顺序不一致、锁嵌套或者持锁时间过长。解决上优先统一加锁顺序、减少锁粒度和持锁时间,避免持锁做 IO 或调用不可控代码。C++ 多锁场景会优先用 std::scoped_lock,数据库场景会统一事务访问顺序、缩短事务并检查索引。最后还会补充超时、日志和监控,防止类似问题再次发生。

  1. Pidfd 是怎么解决pid复用的问题的

PID 是可复用的编号,pidfd 是绑定具体进程实例的稳定句柄。拿到 pidfd 后,即使原 PID 被复用,pidfd 也不会指向新进程。

  1. C++17 新特性
  1. std::optional:表示”可能不存在的值”,比如缓存查询、配置项缺省,比”返回 -1 / 空指针”语义清晰。
  2. std::string_view:只读字符串视图,零拷贝传参,避免临时 string 的堆分配——性能敏感的路径上用。
  3. 结构化绑定:auto [k, v] = map_entry,解构 pair/tuple,遍历 map 和解析返回结构很方便。
  1. 动态链接和静态链接区别在哪,各自应用场景是什么

静态链接是在链接阶段把依赖库中的代码直接链接进可执行文件,所以程序体积较大,但部署简单、运行时对外部库依赖少;动态链接则是在可执行文件中保留共享库依赖,运行时由动态链接器加载 .so 并完成符号解析。动态链接的优势是多个进程可以共享库代码、库升级方便、程序体积小,但需要处理共享库版本和运行环境问题。实际中,独立工具、嵌入式或者强调可移植部署的程序比较适合静态链接,而大型服务、公共组件和插件系统通常更适合动态链接。

  1. 讲讲IO 多路复用,epoll 底层是什么

IO 多路复用的本质是用一个线程同时监听大量 fd 的就绪事件,解决”每个连接一个线程”的问题。演进是 select → poll → epoll:

  • select:fd 集合是位图,有 1024 上限;每次调用要把全部 fd 从用户态拷贝到内核态,内核线性扫描所有 fd 检查就绪,返回后用户态还要再扫描一遍找就绪的——O(n) 且频繁拷贝,n 大了很浪费。
  • poll:把位图换成链表,取消 1024 上限,但仍然是”全量拷贝 + 线性扫描”。
  • epoll:改成事件驱动 + 回调,复杂度从 O(n) 变 O(就绪数)。

epoll 的底层结构(以 Linux 内核实现为准):

  1. epoll_create 在内核创建一个 eventpoll 对象,核心是两个数据结构:一棵红黑树(存放所有注册监听的 epitem,增删改查 O(log n))和一个就绪链表 rdllist(存放已就绪的 epitem)。
  2. epoll_ctl(ADD/MOD/DEL) 就是在红黑树上插/改/删节点;ADD 时还会把回调函数注册到被监听文件的等待队列上(ep_ptable_queue_proc → ep_poll_callback)。
  3. 关键在回调:当文件就绪时(比如网卡收到数据,内核把 skb 挂到 socket 接收队列并唤醒),会调用注册的 ep_poll_callback,把该 epitem 挂到就绪链表上,并唤醒在 epoll_wait 上睡眠的进程。
  4. epoll_wait 做的事:检查就绪链表,有就直接把就绪事件拷贝到用户态缓冲区返回;没有就把当前进程挂到 eventpoll 的等待队列睡眠,超时或被打醒再检查。因为内核只遍历就绪链表,所以效率与总 fd 数无关。

触发模式:LT(水平触发)——只要缓冲区还有数据就每次 epoll_wait 都通知;ET(边缘触发)——只在状态变化(从无到有)时通知一次,所以 ET 必须循环读直到 EAGAIN,否则丢数据。ET 效率更高(通知次数少),但编程要求更高,是经典的坑。

  1. 进程和线程的区别,什么时候用进程,什么时候用线程

进程是操作系统分配资源的最小单位。线程是cpu 调度的最小单位。同一进程的线程共享代码段、数据、堆和大部分进程资源,但各自有栈、寄存器上下文、调度状态和 TLS。进程之间隔离强,一个进程崩溃或内存破坏通常不会直接覆盖另一个进程,但创建、切换和 IPC 成本更高

多线程:高并发,需要频繁共享数据。 多进程:需要故障隔离…

  1. 说一些linux 命令
目标常用命令关注点
文件 / 磁盘pwd、ls -lah、find、stat、du -sh、df -hdu 看目录实际占用,df 看文件系统剩余空间
文本 / 日志grep / rg、head、tail -f、awk、sed、sort、uniq、wc先缩小范围,再统计和定位
进程 / 资源ps、pgrep、top / htop、free -h、vmstat、pidstat、lsof看进程、线程、CPU、内存、打开的 fd
网络ip addr、ip route、ss -lntp、curl -v、ping、nc地址路由、监听端口、HTTP 和 TCP 连通性
调试strace、gdb、coredumpctl、dmesg / journalctl系统调用、core、内核和服务日志
权限 / 服务id、chmod、chown、sudo、systemctl当前身份、权限和服务状态
  1. 讲讲 socket 编程中的 listen 和 accept

客户端 服务端

socket() socket()

| |

| v

| bind()

| |

| v

| listen()

| |

connect() ----------------------->|

| |

| TCP 三次握手 |

|<---------------------------->|

| |

| accept()

| |

send()/recv() <--------------> read()/write()

| |

close() close(connfd)