interview

80 阅读17分钟

基础

1.变量的声明和定义有什么区别

分配内存大小

2.sizeof 和 strlen 的主要区别是什么?

1.运算符,库函数
2.计算内存大小,计算字符串大小
3.编译时计算,运行时计算

3.简述 C/C++ 程序编译时的内存分配情况

  • 内存分配主要涉及 程序的内存布局
  • 编译时:规划内存布局,确定所属的段(Text/Data/BSS),但不分配实际内存
  • 运行时:操作系统根据编译/链接结果加载程序,动态分配堆和栈内存
  • 内存分为静态内存和动态内存,静态内存在程序启动时分配,生命周期持续到程序结束,动态内存在运行时分配,栈自动管理,堆需手动释放。

编译时的关键行为:

  • 1.符号表的生成
    • 编译器收集变量/函数地址的信息(如全局变量Data/BSS),但尚未分配实际内存地址。如int global_val =10;会被标记为“已初始化全局变量”,放入DATA段
  • 2.内存对齐计算
    • 编译器根据平台规则计算在内存中布局,优化访问速度
  • 3.链接阶段的内存分配
    • 地址绑定:连接器将多个目标文件的段合并,例如合并所有目标文件的DATA段,并确定全局变量的最终地址。
1.c++程序内存布局:
Text(代码段):程序指令
Data 数据段:初始化的全局变量和static变量
BSS段 :未初始化的全局变量和static变量
堆:动态分配的内存(new/malloc)
栈: 局部变量、函数参数、函数返回值
  1. 请谈谈你对拷贝构造函数和赋值运算符的理解
  • 拷贝构造函数:在对象创建时通过另一个对象来初始化新对象。默认执行浅拷贝。
  • 赋值运算符:用于将已存在对象的状态赋值给另一个已存在对象
  • 如果对象的成员是指针类型,那么仅复制指针的值(即地址),而不是指针指向的内容,这样会导致 浅拷贝
  • 拷贝构造函数和赋值运算符都需要特别处理深拷贝,避免浅拷贝导致的内存问题

5.左值、右值、左值引用、右值引用、std::move(移动语义)、完美转发

左值:表示一个由明确地址的对象,可以用它修改对象的值。生命周期持久性
右值:临时对象、字面量、函数返回值。生命周期:通常在表达式结束后销毁
左值引用:
右值引用:指向右值的引用。用于支持移动语义和完美转发
std::move:用于将左值转换成右值引用。本身并不移动对象,移动操作由移动构造函数或移动赋值函数实现。避免了拷贝,提升性能。
完美转发:完美转发是一种在模板中保持传递参数的左值/右值性质的技术
templete<typename T>
void test(T &&arg) {
    func(std::farword<T>(arg));
}

网络

1.TCP三次握手

三次握手通过 SYNSYN+ACKACK 三次交互,确保双方收发能力和序列号同步,防止历史连接干扰,可靠建立双向连接。

  1. 第一次握手(SYN): 客户端向服务器发送一个SYN(同步)包,表示请求建立连接。此时,客户端进入SYN_SEND状态,等待服务器确认。
  2. 第二次握手(SYN-ACK): 服务器收到客户端的SYN包后,向客户端发送一个SYN-ACK包,表示同意建立连接,并同步自己的序列号。此时,服务器进入SYN_RECV状态。
  3. 第三次握手(ACK): 客户端收到服务器的SYN-ACK包后,发送一个ACK(确认)包,确认服务器的同步号。此时,客户端进入ESTABLISHED状态,服务器收到后也进入ESTABLISHED状态,双方建立起连接。

三次握手的目的是确保双方都能同步并确认对方的序列号,从而在数据传输过程中确保可靠性和数据完整性。如果某个环节未能完成,连接无法成功建立。

  • 为什么三次?

    • 两次无法防止失效的SYN请求(网络延迟导致重复连接)。
    • 三次确保双方收发能力正常,且序列号同步。

2.TCP 四次挥手

目的安全关闭双向连接,确保双方数据发送完毕,避免数据丢失或资源泄漏。

过程(假设客户端主动关闭):

  1. FIN(客户端→服务端)

    • 客户端发送 FIN=1(结束标志)、seq=u,进入 FIN_WAIT_1 状态,表示客户端不再发送数据
  2. ACK(服务端→客户端)

    • 服务端收到 FIN 后,回复 ACK=1ack=u+1,进入 CLOSE_WAIT 状态,表示已收到关闭请求,但可能仍有数据要发送。
    • 客户端收到 ACK 后进入 FIN_WAIT_2 状态。
  3. FIN(服务端→客户端)

    • 服务端处理完剩余数据后,发送 FIN=1seq=v,进入 LAST_ACK 状态,表示服务端也准备关闭
  4. ACK(客户端→服务端)

    • 客户端收到 FIN 后,回复 ACK=1ack=v+1,进入 TIME_WAIT 状态(等待 2MSL 确保服务端收到 ACK),服务端收到后直接关闭连接。
  • 为什么四次?

    • TCP是全双工的,双方需独立关闭

      • 第一次挥手(FIN)表示客户端不再发数据,但还能收数据。
      • 第二次挥手(ACK)仅确认客户端的 FIN,服务端可能仍有数据要发。
      • 第三次挥手(FIN)表示服务端数据发完,也准备关闭。
      • 第四次挥手(ACK)确保服务端能安全关闭。
  • TIME_WAIT的作用(等待 2MSL):

    TCP 连接中,主动关闭连接的一方出现的状态, 收到对方的 FIN 请求后,发送 ACK 响应,会处于 time_wait 状态

    • 确保最后一个 ACK 到达服务端(若丢失,服务端会重发 FIN)。
    • 让网络中残留的旧数据包失效,避免影响新连接。

    TCP 连接占用的端口,无法被再次使用,会导致新建 TCP 连接会出错,address already in use : connect 异常 TCP 四次挥手关闭连接机制中,为了保证 ACK 重发和丢弃延迟数据,设置 time_wait 为 2 倍的 MSL

    解决办法,服务器端 允许 time_wait 状态的 socket 被重用,so_reuseaddr选项 缩减 time_wait 时间,设置为 1 MSL

MSL,Maximum Segment Lifetime,“报文最大生存时间” 1.任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。(IP 报文)

一句话总结
“四次挥手通过 FINACK 分阶段关闭双向连接,确保数据完整传输,并通过 TIME_WAIT 防止最后一次 ACK 丢失或旧数据干扰新连接。”

3.TCP 和 UDP对比

1.可靠性 tcp面向连接,保证数据可靠传输(3次握手,重传机制) 发送数据不需要先建立连接,直接发送无连接,数据可能丢失,乱序,重复,不可靠

2.流量控制 tcp有流量控制

3.速度 tcp因为有可靠性保障,相对udp较慢

3.头部开销 TCP有序列号,确认号,20字节以上,UDP头部8字节

4.RPC

RPC(远程过程调用,Remote Procedure Call)是一种允许程序在不同计算机或进程间进行通信的协议,使得客户端能够像调用本地函数一样调用远程服务器上的函数。RPC 的核心思想是抽象了网络通信的细节,使得分布式系统中的不同组件可以透明地进行交互。具体工作流程如下:

  1. 客户端调用本地代理函数:客户端发起一个远程调用请求,实际上是调用一个本地的代理函数(也叫存根),这个函数和远程函数接口完全一致,客户端程序认为它在调用本地函数。
  2. 代理函数封装请求:代理函数将调用信息(参数、方法名等)打包成一个请求,发送到网络上的远程服务器。
  3. 服务器接收请求并执行:远程服务器上的相应函数接收到客户端请求后,解包并执行实际的远程函数。执行结果返回给服务器端的代理函数。
  4. 代理函数返回结果:服务器端的代理函数将执行结果封装后,通过网络返回给客户端的代理函数。
  5. 客户端接收结果:客户端接收到返回结果后,继续执行后续操作,用户不需要关心远程调用的具体实现。

RPC 的优势在于其简化了分布式系统的通信开发,程序员无需处理底层的网络通信、数据序列化与反序列化等复杂过程。常见的 RPC 框架包括 gRPC、Apache Thrift、Dubbo 等。通过 RPC,开发者能够轻松实现跨机器、跨语言的服务调用,从而提高系统的可扩展性与灵活性。

5.大端和小端

计算机存储多字节数据(如 int, float)时,有两种排列方式:大端序(Big-Endian) 和 小端序(Little-Endian)。它们的出现主要源于硬件设计的历史差异:

大端序(Big-Endian):
    高字节在前(类似人类书写习惯,如 0x1234 存储为 12 34)。
    早期网络协议(如 TCP/IP)和部分处理器(如 PowerPC、早期的 ARM)使用。
小端序(Little-Endian):
    低字节在前(如 0x1234 存储为 34 12)。
    现代主流 CPU(x86、ARM)默认使用,因其硬件设计更高效。

如何存储?

以 32 位整数 0x12345678 为例:

字节序 内存地址增长方向 →

大端序 12 34 56 78(高字节在前)

小端序 78 56 34 12(低字节在前)

  • 大端序:像大人物一样高调(高字节在前)。
  • 小端序:像小人物一样低调(低字节在前)。

网络字节序(大端序),是网络协议的标准字节序,确保数据在不同平台之间传输时,字节序的统一性。

网络协议(如TCP/IP)在设计时采用了大端序作为数据传输的标准,Python 的 struct 模块默认使用本机的字节顺序来编码和解码数据,通常是小端序。

发送:如果是小端序,转网络字节序(大端序),接收端转换回主机字节序,再解包 int main() {

unsigned int num = 1;
unsigned char *ptr = reinterpret_cast<unsigned char*>(&num);
//使用指针 ptr 指向 num 的地址,并将其强制转换为 unsigned char*,这样我们可以逐字节访问变量 num。
if (*ptr == 1) {
    std::cout << "小端模式" << std::endl;
} else {
    std::cout << "大端模式" << std::endl;
}
//----------
union test
{
    char c;
    int i;
}
test t;
t.i = 1;
if(t.c == 0)
{
    //0000 0001 =》 小端0100 0000,大端0000 0001
    return 小端。
}
 // 网络上传输的数据都是字节流,UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,所以说,网络字节序是大端字节序
return 0;

}

6.IO

IO:输入/输出,IO操作

1.磁盘IO:数据读取,数据写入

2.网络IO:数据请求,数据发送

IO操作->2个阶段:

1.用户进程空间->内核空间

2.内核空间->用户进程空间

LINUX中进程无法直接操作I/O设备,其必须通过系统调用请求kernel来协助完成I/O动作;内核会为每个I/O设备维护一个缓冲区

Linux 中的 I/O 设备分类 在 Linux 中,"每个 I/O 设备"主要指的是所有能够进行输入/输出操作的硬件设备及其抽象,包括但不限于以下类型:

设备类型    示例设备文件  典型用途
块设备 /dev/sda, /dev/nvme0n1  硬盘、SSDUSB存储等
字符设备    /dev/tty, /dev/input/mice   键盘、鼠标、串口等
网络设备    eth0, wlan0 (无设备文件) 网卡
虚拟设备    /dev/null, /dev/zero    特殊用途

内核为不同设备维护的缓冲区:

  1. 块设备缓冲区
  2. 字符设备缓冲区
  3. 网络设备缓冲区(发送缓冲区:sk_write_queue,接收缓冲区:sk_receive_queue)

对于一个输入操作来说,进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备中读取,因为设备IO一般速度较慢,需要等待;内核缓冲区有数据则直接复制到进程空间。

缓存数据指的是内核将最近访问过的磁盘数据保留在内存中的副本,主要包括: 文件内容数据 文件系统元数据(如inode、目录结构等) 磁盘块数据

7.5种IO模型

1. 阻塞IO

  • 特点:调用read/recv时线程全程挂起,直到数据就绪并拷贝完成。
  • 缺点:并发能力差(一请求一线程)。

2. 非阻塞IO

  • 特点:调用立即返回EWOULDBLOCK,线程需轮询检查就绪状态,就绪后仍需同步拷贝数据。
  • 缺点:CPU空转消耗资源。

3. IO多路复用(如select/poll/epoll

  • 特点单线程监听多个fd,就绪后再调用read同步拷贝(仍是同步IO)。
  • 优势:高并发场景核心方案(如Redis/Nginx)。

4. 信号驱动IO(如SIGIO

  • 特点:内核通过信号通知数据就绪,但拷贝阶段仍需线程同步处理。
  • 缺点:信号处理复杂,实际应用少。

5. 异步IO(如io_uring/IOCP

  • 特点:内核全权处理IO,数据就绪并拷贝完成后回调通知,全程无阻塞
  • 优势:真正异步,性能天花板(如Windows IOCP、Linux io_uring)。

关键对比

  • 同步IO:前4种(最终需线程主动参与数据拷贝)。
  • 异步IO:仅第5种(内核全自动处理)。

一句话总结
“五种IO模型按阻塞程度递进,从完全阻塞(阻塞IO)到完全异步(AIO),其中IO多路复用是同步高并发的核心方案,而异步IO是性能终极形态。”

阻塞IO和非阻塞IO区别?

a.阻塞IO:在阻塞IO模型中,当应用程序发起一个IO操作(例如读取文件或网络数据),如果数据没有准备好,该操作会一直阻塞线程的执行。线程会等待,直到数据准备好后才能继续执行后续的代码。这意味着在进行阻塞IO操作期间,线程无法执行其他任务。

b.非阻塞IO:相比之下,非阻塞IO模型允许应用程序在发起一个IO操作后立即返回,并且无论数据是否准备好都可以继续执行后续的代码。如果数据还没有准备好,非阻塞IO模型会立即返回一个错误码或者空数据。应用程序需要通过轮询或者重复尝试来检查数据是否已经准备好。

同步IO和异步IO区别?

同步IO和异步IO区别?

  • 同步IO(Synchronous IO):在同步IO模型中,应用程序发起一个IO操作后会被阻塞等待操作完成。这意味着应用程序必须等待IO操作完成后才能继续执行后续代码。当数据就绪时,系统将数据传输给应用程序并解除阻塞状态,应用程序可以读取或写入数据。同步IO通常以函数调用的形式进行,例如读取文件、网络请求等。

  • 异步IO(Asynchronous IO):在异步IO模型中,应用程序发起一个IO操作后立即返回,并不会阻塞等待结果返回。应用程序可以继续执行后续代码而无需等待。当数据就绪时,系统会通知应用程序进行读取或写入操作,并提供相应的回调函数或事件处理机制来处理完成通知。异步IO常见的实现方式包括回调函数、Promise/Future对象、协程/生成器等。

  • 关键区别:

    • 同步IO需要阻塞等待结果返回,而异步IO则不需要。
    • 同步IO只能进行一次性的单个操作,而异步IO可以同时处理多个任务。
    • 同步IO适合于简单和可预测的IO操作,而异步IO适合于需要同时处理大量任务和高并发的场景
同步IO和阻塞IO区别?

阻塞IO:是同步IO的一种特例,调用时会一直等待数据就绪(全程阻塞线程)

同步IO:包含阻塞IO和非阻塞IO+轮询(如select/poll),最终仍需线程主动等待数据拷贝完成

关键对比

  • 阻塞IO:调用recv()时,从开始等待到数据拷贝完成全程挂起线程
  • 非阻塞IO+轮询(同步非阻塞) :调用recv()立即返回EWOULDBLOCK,线程需轮询检查就绪状态
  • 共同点:数据从内核到用户空间的拷贝阶段,线程都必须同步等待

典型场景

  • 阻塞IO:传统Socket默认模式
  • 同步非阻塞IO:Nginx使用的epoll边缘触发模式

一句话总结
"阻塞IO是同步IO的子集,区别在于是否立即返回。所有阻塞IO都是同步的,但同步IO不一定全程阻塞(如非阻塞IO+轮询)。"

异步IO和非阻塞IO区别?
  • 非阻塞IO需要应用程序自行查询和处理状态变化, 而异步IO则由操作系统负责监测和通知状态变化。
  • 非阻塞IO(同步非阻塞):调用后立即返回(成功或EWOULDBLOCK),但数据就绪后仍需线程主动拷贝数据(同步操作)。
  • 异步IO(如io_uring/IOCP):内核完全接管IO操作,数据就绪后通过回调/信号通知,全程无需线程参与等待(真正的异步)。

关键对比

特性非阻塞IO异步IO
调用返回立即返回(需轮询)立即返回(无需轮询)
数据就绪线程主动检查/拷贝内核自动回调通知
线程阻塞拷贝阶段仍可能阻塞全程无阻塞

典型场景

  • 非阻塞IO:epoll边缘触发(仍需recv同步拷贝)
  • 异步IO:io_uring(Linux)、IOCP(Windows)

一句话总结
“非阻塞IO仍需线程主动处理数据拷贝(同步),而异步IO由内核全权处理并通过回调通知,实现真正的无阻塞。”

epoll

epoll通过红黑树管理fd、就绪链表通知事件、回调机制触发更新,实现了高效的事件驱动模型,成为Linux高并发网络编程的核心机制。

1. 性能维度

  • select:采用1024位固定fd_set,O(n)轮询,适合低并发(<1000连接)
  • poll:动态fd数组,突破select数量限制,但仍是O(n)轮询
  • epoll:红黑树管理fd+就绪链表,O(1)事件通知,支持百万级并发

2. 触发机制

  • select/poll:仅支持水平触发(LT),未处理事件会持续通知
  • epoll:支持LT和边缘触发(ET),ET模式减少无效事件(如Nginx使用ET)

3. 内存效率

  • select:每次调用需重置fd_set,全量拷贝到内核
  • poll:需传递整个pollfd数组
  • epoll:内核维护数据结构,仅返回就绪事件

4. 使用复杂度

  • select:需处理FD_ISSET等宏,代码冗余
  • poll:统一pollfd结构,接口更简洁
  • epoll:分离epoll_ctl(注册)和epoll_wait(等待),扩展性强

5. 跨平台性

  • select:所有平台支持
  • poll:多数Unix-like系统支持
  • epoll:Linux特有(其他系统有类似kqueue)

一句话总结
"select受限于fd数量和性能,poll改进数量限制但仍是轮询,epoll通过红黑树和就绪链表实现O(1)事件通知,成为Linux高并发首选方案,ET模式进一步提升性能。"