面经

  • C++ 的智能指针你用过吗?(主要使用过 shared_ptrweak_ptrunique_ptr

  • C++ 里面总共有多少种智能指针?每一种的用法和应用场景是什么?(三种)

    • unique_ptr
      • 定义:表示对所拥有的对象的独占权,一个对象只能被一个 unique_ptr 拥有
      • 特点:只可以移动,不可以被复制,经常配合 std::move 来使用;
    • shared_ptr
      • 定义:表示对所拥有的对象的共享权,一个对象可以被多个 shared_ptr 拥有;
      • 特点:需要维持一个控制块,来进行引用计数;
    • weak_ptr
      • 定义:不拥有对象,但是 weak_ptr 可以观察 shared_ptr 所拥有的对象;
      • 特点:解决循环依赖问题;作为缓存对象;
  • C++main 函数执行之前,还有哪些函数会先执行?

    • main 之前通常会先执行运行时启动代码,然后完成 全局变量、静态变量的初始化
    • 如果这些对象是 类类型,就会 先调用它们的构造函数
    • 如果它们的初始化表达式里调用了普通函数,这些函数也会在 main 前执行。
    • 程序真正的入口一般不是 main,而是像 _start 这样的运行时入口函数。
    • static 局部变量不属于这个阶段,它是在第一次执行到时才初始化。
  • 如果 const 加在成员函数后面,它的作用是什么?

    • 防止成员函数修改成员变量的状态,也不能调用非 const 成员函数
    • 本质上是把 this 指针变成了指向 const 对象的指针。这样既能保证函数是只读操作,也能让 const 对象调用这个函数。
  • 如果一个链表里有环,怎么判断它有没有环?(通过快慢指针)

  • UDP 你了解吗?(UDP 是一种无连接、不可靠、面向数据报、开销小、传输效率高的传输层协议)

  • UDP 相比 TCP 主要是不可靠传输,如果让 UDP 尽量保证可靠,你觉得可以怎么做?

    • 如果想让 UDP 尽量可靠,本质上就是在应用层自己实现一套可靠传输机制。通常会加上 序列号ACK 确认超时重传乱序重排重复包去重,再进一步配合 滑动窗口流量控制拥塞控制。这样虽然底层还是 UDP,但上层可以做出类似 TCP 的可靠效果。
  • 为什么不直接用 TCP

    • 因为有些场景更看重 低延迟和灵活控制TCP 虽然可靠,但它的 超时重传拥塞控制队头阻塞 等机制有时会影响实时性。基于 UDP 在应用层自己实现可靠传输,可以按业务需求选择 “全可靠” 还是 “部分可靠”,更灵活。
  • 某些场景里不要求完全可靠,只要求一两秒内能到,超过时间就算了,这种 半可靠场景 你有了解吗?

    • 这类场景我理解为半可靠或者时效性优先的传输。它不是要求所有数据最终都到,而是要求数据在有效时间窗口内到达,超时就没有价值了,比如 音视频直播游戏状态同步
    • 实现上 一般会给数据加 序列号时间戳,只在 deadline 内做有限重传,过期包直接丢弃;同时对不同类型的数据分级,关键控制消息走可靠传输高频状态消息走半可靠传输
  • FEC 了解吗?

    • FEC 是指 前向纠错Forward Error Correction),发送方在原始数据之外,额外发送一些冗余校验数据,这样接收方即使丢了一部分包,也有机会自己恢复出来,而不用等重传。
  • P2P 打洞这块,如果两端都是对称 NAT,应该怎么处理?

    • 使用中继服务器
  • 平时用过抓包工具吗?

    • Linux 服务器上我一般用 tcpdump 抓包,因为它轻量、命令行方便;
    • 抓下来的 pcap 文件再放到 Wireshark 里做详细分析,进行排查相关问题;
  • 自旋锁是怎么实现的?(spinlock

    • 原子操作 去竞争一个锁变量,比如通过 CASexchange 把锁状态从 未加锁 改成 已加锁
    • 如果修改成功,线程进入临界区;
    • 如果修改失败,说明锁被其他线程持有,当前线程不会睡眠,而是一直循环重试,直到锁被释放;
    • 优点:避免线程阻塞和上下文切换,适合临界区非常短的场景;
    • 缺点:持续占用 CPU,如果锁持有时间长或者竞争激烈,就会浪费很多 CPU 资源;