复习

41 阅读13分钟

基础

1.linux常用命令

2.tcpdump

  • 基于 Libpcap 库tcpdump 的核心功能依赖于 libpcap 库,这个库提供了捕获网络数据包的通用接口。
  • 混杂模式:默认情况下,网卡只会接收发给本机的数据包。tcpdump 可以将网卡设置为混杂模式,使其能捕获流经网络接口的所有数据包(限于同一广播域内),而不仅仅是发给本机的。
  • 内核空间到用户空间:数据包首先被网卡驱动接收到,然后经过内核的协议栈。libpcap 通过一种高效的机制(如 BPF)在内核层对数据包进行过滤和缓冲,最后拷贝到用户空间供 tcpdump 处理显示

3.智能指针

核心思想RAII机制,利用类的性质在构造函数申请资源,西构函数自动释放资源。

独占unique指针,同一时刻只允许一个指针访问同一个对象,原理是禁止了拷贝构造和赋值操作,通过移动语义

共享指针,是使用引用计数允许多个指针指向同一个资源。当计数为0时,西构函数自动释放

week指针:用于观察shared指针的资源,解决共享指针循环引用的问题:2个shared指针相互引用

make_shared通过一次内存分配完成对象和控制块的创建,原子操作,所以不要使用new,不安全

shared_ptr的底层结构:底层包括2个裸指针,一个指向管理的对象,一个指向包括引用计数的控制块

Delete []是如何知道删除多大内存的

  • delete[]本身并不知道数组的大小。这个信息是在之前使用new[]分配数组时,由C++运行时库在堆上分配的一块额外空间(通常称为Cookie)存储起来的。这块空间位于返回给用户的指针之前。

malloc/new 分配的内存到底可以分配多大?

  • 在32位系统上,单个进程的用户态堆空间通常只有2-3GB。newmalloc的每次分配还有额外的内存对齐要求和内存块管理开销(类似Cookie),所以实际能分配的连续内存块远小于理论值。分配极大内存(如几个GB)几乎肯定会失败。

malloc(0)new T[0] 的行为是什么?

  • C++标准允许这种行为。它们可能返回一个非nullptr的无效指针malloc(0)),或者一个合法的指针(new T[0]),但你都不能对其进行解引用操作。对应的freedelete[]必须被正常调用以避免内存泄漏。

const 变量的值是否真的不可修改

  • 通过指针或引用进行强制类型转换(const_cast),可以移除const属性并修改值。但是,如果这个变量本身被编译器优化放入只读数据段(.rodata),修改它会引发运行时崩溃(如Segment Fault)。这是「未定义行为(UB)」

空类 class Empty{}; 的大小是多少?为什么?

  • sizeof(Empty) 结果为 1。C++要求每个对象都必须有唯一的地址。如果大小为0,那么一个空类数组中的每个元素都会有相同的地址,这是不允许的。编译器会自动插入一个占位字节来保证唯一性。

sizeofstrlen 在用于字符串字面量时有什么区别?

  • sizeof("Hello") 返回 6(包括末尾的'\0'空字符),因为它计算的是整个字符数组的大小。strlen("Hello") 返回 5,因为它计算的是到'\0'之前的字符个数。

C++中对象的生命周期是从什么时候开始的?

  • 懵点:从构造函数执行开始?
  • 真相:对象的生命周期是从构造函数成功完成的那一刻开始的。在构造函数函数体执行期间,对象还没有正式「诞生」。因此,在构造函数中调用虚函数,不会发生多态行为,只会调用当前类定义的版本。

inline 关键字的真正作用是什么?

  • 让编译器在调用函数的地方直接插入函数体代码,而非执行常规的函数调用(如压栈、跳转、弹栈),以此减少函数调用开销并可能优化代码执行效率
  • 内联的优势仅针对 “高频调用的小函数”。如果函数体大(比如几十行代码),内联后会导致代码段体积增大,可能超出 CPU 缓存容量,反而因 “缓存命中率下降” 变慢。
  • inline的主要作用是避免多重定义错误。它告诉链接器:这个函数(或变量)在多个编译单元中定义是相同的,请只保留一份

菱形继承」问题是什么?如何解决?

  • 类B、C都继承了A,类D继承了B,C,这是D产生了菱形继承,D包含了2份A的子对象,调用产生歧义性。使用虚继承,class B : virtual public Aclass C : virtual public A

const int*int* constconst int* const 的区别是什么?

  • 记忆口诀:const 修饰谁,谁就不能被修改(看 const 右侧紧邻的内容)
  • const int *p,内容不能修改(不能通过指针 p 来修改它指向的那个整数的值,- 指针能修改:指针变量 p 本身的值(即它存放的地址)是可以改变的,你可以让它指向另一个内存地址。)
  • int* const p:p指针不能修改,指向的内容可以修改
  • const int* const p: p指向的内容和指针都不能修改

为什么拷贝构造函数的参数必须是引用类型?

  • 如果拷贝构造函数参数是值传递,会引发 无限递归

    • 调用拷贝构造函数时,需要将实参复制给形参,而复制实参又会调用拷贝构造函数,形成递归调用,最终导致栈溢出。
  • 因此,C++ 标准强制要求拷贝构造函数的参数必须是引用(通常是 const 引用,避免意外修改源对象)

static 成员函数为什么不能调用非 static 成员变量 / 函数

  • 因为静态成员函数没有 this 指针,而非静态成员变量和函数的访问都依赖于一个具体的对象实例(即 this 指针

说说static

  • static是C++中一个多功能关键字,它的含义主要看它用在哪里。
  • 在函数内部用于局部变量时,它让变量的生命周期变成整个程序周期,而不是函数调用周期
  • 在全局作用域用于变量或函数时,它将其链接性从外部链接改为内部链接,使其成为当前文件私有的
  • 在类里面用于成员变量和函数时,它使成员属于类本身而不是对象实例。所有对象共享静态变量,而静态函数没有this指针,因此不能直接访问普通成员
  • 总的来说,static的核心思想就是控制变量或函数的存储期和可见性,实现某种程度的‘持久化’或‘私有化

什么是右值引用,什么是移动语义(Move Semantics)?std::move的作用是什么,什么是完美转发

  • 右值引用是一种引用类型,语法为 T&&,它专门用于绑定右值

    • 临时对象(如函数返回值 T()
    • 字面量
    • std::move()产生的对象
  • 传统的引用(左值引用 T& :只能绑定到有名字、有持久状态的左值(如变量)

  • 移动语义允许我们将资源(如动态内存、文件句柄等)从一个对象 “转移” 到另一个对象,而不是大量资源(如堆内存)拷贝,它通过移动构造函数移动赋值运算符来实现。

  • std::move 本质是一个类型转换函数,它的作用是将左值强制转换为右值引用

    • 它不移动std::move 只是一个 static_cast<T&&> 的封装,它在编译期完成类型转换
    • 移动操作实际发生在接收这个右值引用的函数里(比如移动构造函数或移动赋值运算符)。

什么是粘包拆包

  • 粘包和拆包是指在基于流的网络传输协议(如TCP)中,由于协议本身没有消息边界,发送方写入的多个数据包在接收方读取时,可能会被粘成一个大的数据包,或者被拆分成多个小包的现象

  • TCP是面向流的协议:TCP协议不关心应用层数据包的具体含义,它只保证数据字节流能够按顺序、可靠地传输。它把应用层交下来的数据仅仅看成是一连串的、无结构的字节流。

  • 根本原因:  TCP协议本身没有“消息包”的概念,它只有“字节流”。TCP是流式协议,没有消息边界

  • TCP有缓冲区:发送端和接收端都有缓冲区来提高传输效率。

    • 发送缓冲区:发送方应用程序调用write写入的数据可能不会立刻发送出去,可能会在缓冲区中等待与后续的数据合并,然后一次性发送(Nagle算法也可能导致此行为)。这可能导致粘包
    • 接收缓冲区:接收方收到的数据包会先存放在接收缓冲区。接收方应用程序调用read读取时,是从缓冲区中取走数据,一次读取的数据大小取决于当前缓冲区的数据量和应用程序指定的读取大小。这可能导致拆包
  • 粘包和拆包的场景

    • 粘包
      • 场景1:发送方造成
        发送方频繁发送小数据包,由于Nagle算法(旨在减少小包数量)或缓冲区积压,将多个应用层数据包合并成一个大的TCP报文段发送出去。
      • 场景2:发送方造成
        发送方频繁发送小数据包,由于Nagle算法(旨在减少小包数量)或缓冲区积压,将多个应用层数据包合并成一个大的TCP报文段发送出去。
    • 拆包
      • 场景1:数据包大于缓冲区
        发送方发送的一个应用层数据包太大,超过了接收方套接字的接收缓冲区大小(或TCP的MSS),这个大数据包就会被拆分成多个TCP报文段传输。
      • 场景2:数据包大于应用程序读取大小
        接收方应用程序一次read调用指定的读取长度,小于当前接收缓冲区中某个数据包的长度,那么一次读取就只能拿到一个完整数据包的一部分,需要多次读取才能拼凑完整。
  • 解决方案

    • 定长法
    • 分隔法,特殊字符
    • 包头,包长

epoll/select/poll

  • epoll:事件回调机制: 避免使用遍历, 而是使用回调函数的方式

    • 时间复杂度O(1).
    • 没有数量限制,文件描述符数目无上限
  • LT

    • 当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分
    • 假设只读了1K数据, 缓冲区中还剩1K数据, 在第二次调epoll_wait 时, epoll_wait仍然会立刻返回并通知socket读事件就绪. 直到缓冲区上所有的数据都被处理完, epoll_wait 不会立刻返回
    • 支持阻塞读写和非阻塞读写
  • ET

    • 当epoll检测到socket上事件就绪时, 必须立刻处理.如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候,epoll_wait 不会再返回了
    • ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会
    • 只支持非阻塞的读写
  • select/poll

    • select() 和 poll() 系统调用的本质一样,管理多个描述符也是进行轮询,根据描述符的状态进行处理(epoll只有状态变化的时候才触发,就绪《=》非就绪,一直就绪状态即有数据也不会触发),但是 poll() 没有最大文件描述符数量的限制.

什么是栈溢出(Stack Overflow)?如何避免?

  • 递归调用过深、大型局部变量数组等导致调用栈空间耗尽。
  • 避免使用巨大的局部变量(使用动态分配或在全局区分配)。
  • 控制递归深度,或改为迭代算法。

为什么在嵌入式等领域通常会禁用 RTTI?

请说明 std::unordered_map 的底层原理、平均和时间复杂度

  • 原理:基于哈希表实现。它使用一个数组作为桶,通过哈希函数将键(Key)转换为数组的索引。如果发生哈希冲突,则使用链地址法在同一个桶内用链表存储冲突的键值对。

  • 时间复杂度

    • 插入、查找、删除操作的平均时间复杂度为 O(1)
    • 最坏情况下(所有键都冲突到同一个桶,即退化为一个长链表),时间复杂度为 O(n)

针对项目

skynet

通过自动、用户主动调用接口等方式收集、汇总、存储数据,(数据上云)同时通过可视化界面为用户提供多维度的实时和历史数据、异常报告等,方便用户快速发现和定位问题。

  • 系统基础指标
  • 进程指标
  • 系统稳定性指标,coredump oom
  • 其他指标,进程cpu调度等待时间, runqslower工具用于跟踪进程在运行队列中等待被调度的时间;通信时延,trace

1.postgresSQL的选择

  • 关系型数据库存储后端(特别是用于存储仪表板、用户、组织、警报等元数据)
  • Grafana的数据源可以配置为Prometheus、InfluxDB、TimescaleDB(基于PostgreSQL的时序数据库)
  • PostgreSQL,JSON支持 | JSONB,二进制存储,支持索引,查询性能极高
  • 官方强烈建议生产环境使用PostgreSQL

Service

  • 通信接口的封装,针对工具链、SOA服务的用户,集成后可快速构建通信服务
  • someip
  • skelten、proxy
  • ProviderBase向上主要提供的功能是:注册Method接口、推送Topic接口、启动服务接口、停止服务接口
  • ConsumerBase向上主要提供的功能是:调用Method接口、订阅Topic接口,接收Provider推送的服务状态消息。select() 和 poll() 系统调用的本质一样,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制

Log

  • 共享内存
  • login_engine(dlt-deamon)、logclient