C++ 后端基础面经
1. 编译器优化都干哪些事情?
编译优化的目标是程序更小更快,不浪费空间,不做多余操作,主要干的几件事:
- 删除定义但未使用的代码段;
- 尽可能的将频繁访问的局部变量、中间计算值缓存到 CPU 寄存器减少内存占用,因为 CPU 内存访问速度比寄存器要慢很多;
- 编译期直接计算固定常量表达式,运行时不再重复运算;
- 循环优化:把循环内不会变化的计算提到循环外部,避免每轮循环重复执行;
- 内存布局优化:自动重排结构体成员顺序、压缩内存布局、删除多余对齐填充 Padding;
- 指令重排与流水线优化:在不破坏代码逻辑的前提下,调整指令执行顺序,适配 CPU 流水线,提升并行执行效率。
2. HTTP 可以一次传输多种类型的数据么?
可以。
multipart/form-data:请求侧上传多个资源,用自定义 boundary 分隔多个数据段,每段独立声明自身数据类型;multipart/related:响应侧同时返回多个资源。
3. volatile 和 atomic 都可以解决强制把变量刷回内存,为什么不用 volatile 替换 atomic?
两者共性:
- 都禁止变量缓存到寄存器;
- 每次读写强制走内存,保证内存可见性、禁止编译器优化。
核心区别:
-
volatile:仅保证内存可见性,不保证原子性;如自增是读 - 改 - 写三步操作,多线程下会指令穿插,出现覆盖写、计数丢失,线程不安全;
-
atomic:内置 volatile 全部能力,额外提供:
- 硬件级原子操作,单条 CPU 指令完成读写 / 自增;
- 提供 CPU 硬件指令重排屏障,支持
memory_order自定义内存序。
4. 了解字节序么
- 大端序:高位字节存低地址,符合人类阅读习惯;
- 小端序:低位字节存低地址,x86 架构默认采用小端;
- 网络传输统一使用大端序;
- Linux x86 小端平台收发 TCP 数据时,需通过
htonl/htons/ntohl/ntohs完成大小端转换。
5. 子类转向父类实现动态多态的过程
- 子类对象内存布局中,虚指针统一存储在类对象起始位置;父类引用 / 指针指向子类对象时,可正常拿到子类虚表,调用子类重写的虚函数;
- 子类重写的虚函数,编译器会隐式绑定子类
this指针;即便通过父类指针调用,依然可以正常访问子类独有成员变量。
6. vector、list 底层实现、优缺点
vector
- 底层:连续线性内存,由 头指针、有效尾指针、容量尾指针 三个指针维护;
- 优点:支持下标随机访问、缓存命中率高、尾插尾删效率高;
- 缺点:中间 / 头部插入删除效率低、扩容存在内存拷贝开销、会有冗余容量造成空间浪费。
list
- 底层:双向循环链表,节点分散堆内存;
- 优点:任意位置插入删除 O (1)、按需分配内存、无扩容与空间冗余;
- 缺点:不支持随机访问、缓存失效严重、只能遍历查找。
7. 左值、右值 与 push_back /emplace_back 扩展
cpp
运行
Myclass obj;
vec.push_back(obj); // 左值传入,触发拷贝构造,开销大
vec.push_back(std::move(obj)); // 左值强转右值,触发移动构造
vec.push_back("tmp"); // 构造临时右值对象,再移动构造
vec.emplace_back("tmp"); // 直接在容器内存原地构造,无拷贝无移动
关键总结:
-
栈上普通成员(int、数组、自定义非指针成员):移动构造和拷贝构造一致,都是浅拷贝,无性能优势;
-
堆指针成员:移动语义仅拷贝指针地址(浅拷贝),转移堆资源所有权并置空原指针,避免深拷贝;
-
拷贝区分:
- 栈变量:拷贝一律为浅拷贝,直接复制数值;
- 堆变量:浅拷贝只复制指针地址,深拷贝会重新开辟堆内存并复制数据;
-
移动语义本质:浅拷贝 + 原资源置空,防止二次析构、野指针问题。