C++编译与内存相关

1.cpp文件从解析到执行发生了什么?

  • 预处理:.cpp文件经过预处理生成文本文件.i格式的文件。在这个过程中,对原始的cpp文件中删除注释,展开宏等等。以方便进行下一步处理
  • 编译+汇编:依据在预处理阶段生成的.i文件,进行词法、语法分析和语义检查,之后使用机器字进行替代,转为由汇编语言组成的.o文件
  • 链接:根据编译阶段生成的.o文件,以及静态链接库.a或者动态链接库.so。这由链接方式决定。将.o文件和其执行所需要的库文件进行链接,生成最终的可执行文件。此时也是在这个阶段,.o文件被组合为一个单独的可执行文件,并且生成一个ELF格式的文件。这个ELF文件包括了:可执行代码(.text段),数据(.data段),未初始化数据(.bss段),动态链接信息(如果需要),调试信息(如果启用)。**最终可执行文件的格式就是ELF文件。**ELF文件中保存的内容是编译后运行前生成的内容
  • 加载:在程序执行时,操作系统的加载器会将ELF文件加载到内存中。加载器会根据ELF文件的头部信息(如程序头表和节区头表)确定各个段(如 .text.data等)的内存位置,并将它们加载到内存中。随后,程序开始执行。

2.函数调用过程中内存发生了什么?

现有如下代码:

1
2
3
4
5
6
7
8
9
10
void foo(int a) {
int b = a + 1;
// 函数执行完毕
}

int main() {
int x = 10;
foo(x); // 调用foo函数
return 0;
}
  • 栈帧
    • 栈帧是每个函数调用时在栈中分配的内存区域,它包含了函数的返回地址、调用者的栈帧指针、局部变量、参数等信息。栈帧的管理是栈内存结构的核心,每次发生新的函数调用,都会由新的栈帧被创建。通常存在寄存器 %rbp
  • 栈顶
    • 栈顶指向的是栈内存顶部的区域,它是栈中当前活动的地址位置。通常存在寄存器 %rsp
  • 发生函数调用时:首先新创建一个新的栈帧,然后将调用者调用的函数结束后的下一条指令的地址压入栈中(返回地址)。然后在将调用者的栈帧的栈指针压入栈中,以及一些上下文的寄存器状态。之后在从右到左依次向栈中压入函数参数,之后在压入一些局部变量。遵循着这样的顺序。

3.申请堆内存产生的内存碎片

  • 由于程序申请堆内存空间是动态的,而堆内存的管理是操作系统或者内存管理程序分配或者回收的,因此不可避免的会产生内存碎片。
  • 如何解决内存碎片:
    • 内存池技术:提前分配好内存,用内存池来管理
    • 垃圾回收机制:标记和整理内存来回收未使用的内存,以此来减少碎片
    • 最佳适配算法:动态分配时选择最适合需求大小的内存块,避免过多的小碎片
    • 合并内存空间:释放内存时,尝试将相邻的空闲内存块合并成更大的块

4.CPP的内存对齐

  • 每种数据类型都有一个对齐要求,即它的起始地址必须是该对齐值的倍数。
  • 要求内存对其可以减少访问内存的延时,来提升性能。

5.智能指针的介绍与使用

  • std::unique_ptr:是一种独占式的智能指针,意味着同一时刻只能有一个 unique_ptr 指向某个对象。同时 unique_ptr不允许赋值,只能通过 std::move转移所有权。当独占式指针超出作用域后,会自动销毁。
  • std::share_ptr:是一种共享的只能指针,多个 shared_ptr 可以共同拥有同一个对象,智能指针内部会使用引用计数来追踪有多少 shared_ptr 指向同一个对象。当这个引用计数为0的时候,就会释放掉共享指针
  • std::weak_ptr:是一种弱指针,std::weak_ptr 不影响对象的引用计数,它用于观察一个由 shared_ptr 管理的对象。用于避免由于循环引用(指两个struct中互相有指针互相引用,导致互相持有无法释放,用 weak_ptr就可以解决这个问题)导致 shared_ptr无法释放

总结:std::unique_ptr通过判断在不在作用域来销毁,而 std::share_ptr通过内部的引用计数来判断是否需要销毁。

  • 在使用智能指针的时候,尽量避免手动new,这样会需要两次手动内存分配

6.大端与小端

  • 大端存储:高字节在高位,符合人类手写习惯。适用于网络通讯协议
  • 小端存储:低字节在低位,更适合计算机的运算。常见于x86_64机器

7.内存泄漏

  • 内存泄漏:指的是程序动态分配的内存未被释放,导致内存长期占用,最终可能导致内存耗尽、程序崩溃等问题。比如new了一个堆空间,但是一直没有delete
  • 如何避免内存泄漏:
    • 避免手动管理内存,使用智能指针(C++)
    • 使用 RAII 资源管理(C++):将资源(内存、文件、锁、数据库连接等)与对象的生命周期绑定,确保资源在对象销毁时自动释放,防止资源泄漏
    • 解决循环引用问题和避免悬垂指针

8.include

  • include “ “和include<>的区别:include “ “首先从当前源文件所在目录或用户指定的目录中搜索头文件,如果找不到,则去库文件中寻找。而include<>则是直接去库文件中寻找对应的头文件
作者

kosa-as

发布于

2025-03-18

更新于

2025-03-18

许可协议

评论