# 亚信安全C++一面面经
# 1、C++重载如何实现?
答: 实现原理: C++函数重载是通过名称修饰(Name Mangling) 技术实现的,编译器在编译时会对函数名进行改编,将参数类型信息编码到新的函数名中。
具体过程:
编译器检查函数名相同但参数列表不同的函数
为每个重载函数生成唯一的修饰名(如:_Z3addii、_Z3adddd)
在调用时根据实参类型选择匹配的函数版本
int add(int a, int b); // 修饰名可能是: _Z3addii
double add(double a, double b); // 修饰名可能是: _Z3adddd
2
重载条件: 函数名相同, 函数列表不同(参数类型、个数、顺序), 与返回类型无关
# 2、设计了一个动态链接库,里面有一个和标准库函数同名的函数,在C语言中如何使用?(extern关键字)
答: 动态库中有与标准库同名的函数,需要在C语言中使用原标准库函数。
使用extern "C"链接指示符: 比如
// 在C++中声明C语言链接方式
extern "C" {
#include <stdlib.h> // 包含标准库头文件
}
// 使用动态库中的同名函数
extern void* my_malloc(size_t size); // 动态库版本
// 在C代码中明确使用标准库函数
void* std_malloc = malloc(100); // 使用标准库malloc
void* my_malloc = my_malloc(100); // 使用动态库版本
2
3
4
5
6
7
8
9
10
11
extern "C"禁止C++的名称修饰,保持C语言的链接约定
这样可以避免符号冲突,明确指定使用哪个版本的函数
# 3、虚函数如何实现动态绑定?虚函数指针和类对象是如何绑定起来的?
答: 虚函数通过虚函数表(vtable) 和虚函数指针(vptr) 实现动态绑定。
绑定过程:
每个包含虚函数的类都有一个虚函数表
每个对象包含一个指向vtable的指针(vptr)
调用虚函数时,通过vptr找到vtable,再通过偏移量调用正确函数
# 4、构造函数、析构函数可以是虚函数吗?
答: 构造函数不能是虚函数
原因: 构造时对象尚未完全创建,虚指针vptr还未初始化
虚函数调用需要通过vptr,但此时vptr可能无效, 构造函数的作用是创建对象,不需要动态绑定
析构函数: 基类的析构函数应该是虚函数
原因:确保通过基类指针删除派生类对象时,能正确调用派生类的析构函数,避免内存泄漏和资源未释放问题
实例:
class Base {
public:
virtual ~Base() { cout << "Base destructor"; } // 虚析构函数
};
class Derived : public Base {
public:
~Derived() override { cout << "Derived destructor"; }
};
Base* obj = new Derived();
delete obj; // 正确调用Derived和Base的析构函数
2
3
4
5
6
7
8
9
10
11
12
# 5、TCP和UDP的区别?
答:
| 特性 | TCP(传输控制协议) | UDP(用户数据报协议) |
|---|---|---|
| 连接性 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输,有确认机制 | 不可靠传输,尽力而为 |
| 顺序性 | 保证数据顺序 | 不保证顺序 |
| 速度 | 较慢(有握手和确认) | 较快(无额外开销) |
| 头部大小 | 20-60 字节 | 8 字节 |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有 | 无 |
| 应用场景 | 文件传输、Web、邮件 | 视频流、DNS、游戏 |
# 6、TCP的粘包,如何解决?
答:
- 粘包原因:
TCP是字节流协议,没有消息边界, 发送方多次发送的数据可能被接收方一次接收,
网络延迟和Nagle算法可能合并小数据包
- 解决方案:
定长消息,固定每个消息长度为100字节
分隔符协议,使用特殊字符作为消息边界,如"\r\n"
长度前缀:用4字节长度 + 实际数据这样的消息格式
应用层协议:在HTTP层使用Content-Length字段,在自定义协议层定义消息头结构
# 7、Linux的内核机制有哪些?
答:有进程管理,内存管理,文件系统,设备驱动,网络线,系统调用这些。
| 机制类别 | 主要功能 | 相关组件 |
|---|---|---|
| 进程管理 | 进程调度、创建、终止 | 调度器、fork、exec |
| 内存管理 | 虚拟内存、物理内存分配 | 页表、SLAB分配器 |
| 文件系统 | 文件操作、VFS抽象层 | ext4、VFS、inode |
| 设备驱动 | 硬件设备抽象管理 | 字符设备、块设备 |
| 网络栈 | 网络协议实现 | TCP/IP、套接字 |
| 系统调用 | 用户态-内核态接口 | syscall表、glibc |
# 8、进程、线程、协程有哪些区别
答:
| 特性 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 定义 | 资源分配单位 | CPU调度单位 | 用户态轻量级线程 |
| 隔离性 | 完全隔离(虚拟地址空间) | 共享内存空间 | 共享线程资源 |
| 创建开销 | 大(需要复制资源) | 小 | 极小(仅需栈空间) |
| 切换开销 | 大(需要切换页表) | 中(需要内核介入) | 小(用户态切换) |
| 通信方式 | 管道、信号、共享内存等 | 全局变量、互斥锁等 | 直接共享变量 |
| 并发性 | 进程级并发 | 线程级并发 | 协程级并发 |
| 适用场景 | 独立任务、安全隔离 | CPU密集型、资源共享 | I/O密集型、高并发 |
# 9、多线程的资源同步有哪些机制?自旋锁,什么时候用,有什么好处?
答: 有互斥锁,自旋锁,读写锁,条件变量,信号量这些机制。
| 机制 | 原理 | 适用场景 | 特点 |
|---|---|---|---|
| 互斥锁 | 二进制锁,互斥访问 | 临界区保护 | 可能阻塞,有上下文切换 |
| 自旋锁 | 循环检查锁状态 | 短临界区、多核CPU | 忙等待,无上下文切换 |
| 读写锁 | 读写分离,多读单写 | 读多写少场景 | 提高读并发性 |
| 条件变量 | 等待特定条件成立 | 生产者-消费者模型 | 需要与互斥锁配合 |
| 信号量 | 计数器控制访问数量 | 资源池、限流 | 可控制多个线程访问 |
自旋锁使用场景: 临界区执行时间很短(小于两次上下文切换时间)
多核CPU环境(单核CPU使用自旋锁浪费CPU)
不能睡眠的上下文(如中断处理程序)
# 10、内存隔离、虚拟内存、页面换入换出(swap区)等
答:
- 内存隔离
内存隔离是操作系统保护不同进程内存空间不被非法访问的安全机制。
每个进程都认为自己独享整个内存空间。
内存隔离的好处有
安全性:进程无法访问其他进程的内存
稳定性:一个进程崩溃不会影响其他进程
简化编程:每个进程都有统一的地址空间视图
- 虚拟内存
虚拟内存 = 物理内存 + 磁盘交换空间
它通过内存管理单元和页表机制,为每个进程提供一个统一的、巨大的、独立的虚拟地址空间,这个空间与实际的物理内存大小无关。
-页面换入换出
当物理内存不足时,操作系统将暂时不用的内存页面换出到磁盘,为急需的页面腾出空间。
常见的页面替换算法对比
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| FIFO(先进先出) | 替换最早进入内存的页面 | 实现简单,开销小 | 性能差,存在贝拉迪异常(分配更多页框可能导致缺页率上升) |
| LRU(最近最少使用) | 替换最久未被访问的页面 | 性能优秀,接近最优算法 | 实现复杂,需要硬件(如移位寄存器或栈)支持,开销大 |
| Clock(时钟/二次机会) | FIFO的改进,检查页面的访问位,为访问过的页面提供第二次机会 | 平衡了性能与实现复杂度,开销较小 | 性能不如LRU精确,是LRU的近似算法 |
| LFU(最不经常使用) | 替换访问频率最低的页面 | 适合长期的工作负载分析 | 难以有效应对访问模式变化,可能积累“历史垃圾” |
# 11、fork函数返回值,父进程子进程分别返回什么?fork以后子进程从什么位置开始继续运行?
答: fork函数行为
#include <unistd.h>
pid_t fork(void);
2
返回值:
父进程:返回子进程的PID(> 0)
子进程:返回0
错误:返回-1
执行位置: 子进程从fork()调用之后的下一条指令开始执行
继承父进程的代码段、数据段、堆栈的副本,获得父进程打开的文件描述符的副本
# 12、项目延伸:两个客户端之间可以不依靠服务端直接P2P连接吗?
(面试官说了个NAT,还有什么UDP打洞,这块不是很了解。)
答: NAT(网络地址转换)问题:
内网设备没有公网IP,无法直接建立连接,NAT设备阻止外部主动发起的连接
- UDP打洞技术:
打洞过程:
注册阶段:两个客户端分别连接公网服务器进行注册
地址交换:服务器交换两个客户端的NAT映射信息
打洞尝试:客户端同时向对方的NAT映射地址发送UDP包
建立连接:NAT设备创建临时映射规则,允许P2P通信
← 百度测试开发一面面经 宇视嵌软线下技术面 →
评论
验证登录状态...