【QT入门到晋级】内存
前言本文从“在C/C开发时选用哪种内存处理方式较好”的角度聊聊内存的使用。主要还是站在需要开发后台功能的QT开发者的角度来写的总结笔记。内存分布我们开发的应用程序运行时为了确保确保系统稳定性通常应用程序是在用户态下运行只能访问操作系统为其分配的用户空间内存。临时变量、局部变量等临时值通常存储在用户态的栈空间或寄存器中全局变量、静态常量、静态数组等生命周期为整个程序运行期的通常存储在用户态的静态存储区数据段或BSS段动态数组如new、malloc、calloc、realloc等申请的内存、 STL容器(vector、list、map) 等通常存储在用户态的堆空间中通过malloc或new申请动态内存时尝试从用户态的堆内存池中分配内存若用户态堆中有足够空闲内存块则直接返回给应用程序若用户态的堆内存不足时C库会通过系统调用如brk或mmap向内核申请扩展堆空间应用程序不需要关注此操作。通过内存映射文件技术(mmap)申请的内核动态内存内核会为用户态应用进程分配虚拟地址空间这个虚拟机地址空间是在用户态地址空间中分配并建立与物理内存或文件的映射关系。以下是单个应用进程3类分布区对比声明以下表格来源于AI辅助创作特性栈(Stack)静态存储区(Static Area)堆(Heap)典型应用场景临时变量、局部变量全局变量、静态常量、静态数组动态数据、大对象分配方式编译器自动分配/释放调整栈指针编译期固定地址分配运行时动态分配malloc/new访问速度最快仅次于CPU寄存器次之直接寻址最慢间接寻址生命周期函数调用周期程序运行周期手动控制或GC管理内存连续性连续存储连续存储非连续链表结构大小范围Linux: 2-10MBWindows: 1-8MB单变量/数组不超过几百MB理论是所有系统剩余虚拟内存申请越多碎片越严重管理开销无自动管理无编译期确定高需维护空闲链表/内存池线程安全线程独占无需同步可能需同步全局共享需同步多线程共享碎片问题无无易产生碎片分配失败后果栈溢出程序崩溃编译期错误返回NULL需错误处理开发运用栈的应用栈空间是最小的也是执行最快的适合存放小型、生命周期短且大小固定的数据因为有大小限制如果申请大数据容易导致程序崩溃例如int arr[1000000]约4MB可能超出栈默认大小引发栈溢出。以下通过读取文件内容的场景来对比//正确方式不管文件有多大每次读取1KB到栈中 std::ifstream file(large_file.bin, std::ios::binary); char buff[1024]; // 1KB栈缓冲区 while (file.read(buff, sizeof(buff))) { // 处理当前块buff中数据 } //错误方式10MB栈缓冲区导致程序崩溃 std::ifstream file(large_file.bin, std::ios::binary); char buff[1024*1024*10]; // 10MB栈缓冲区 while (file.read(buff, sizeof(buff))) { // 处理当前块buff中数据 }如果硬要申请很多的数据缓存可以把数组char buff[1024*1024*10];搬到外面作为静态数组用或者改成vector(动态内存)方式来用下面给出改成动态内存方式的样例const size_t buffer_size 1024 * 1024 * 20; // 20MB缓冲区 std::vectorchar buff(buffer_size); // 堆分配缓冲区 std::ifstream file(large_file.bin, std::ios::binary | std::ios::ate); while (file.read(buff.data(), sizeof(buff))) { // 处理当前块buff中数据 }总结临时变量、局部变量不能申请太大的数组缓存主要是关注大数组或者是包含大数组的结构体即可。静态存储区的应用静态存储区与栈很接近运行效率稍微比栈低但空间比栈大而且作用域是整个全局如果频繁要用到某个常量/变量/数组那么就适合设置为全局变量、静态常量、静态数组。全局变量、静态常量太常用了这里举个静态数组的场景处理指定目录下存在待入库到mysql的文件// 数据结构体 struct stCollectMsg { int id; char name[64]; double value; char timestamp[20]; }; const int BATCH_SIZE 100; stCollectMsg sqldata[BATCH_SIZE]; // 静态数组 // 文件读取函数 void ReadFile(const char* filename) { std::ifstream file(filename, std::ios::binary); int current_count 0; while (!file.eof()) { //读取数据 file.read((char*)sqldata[current_count], sizeof(stCollectMsg)); if (file.gcount() sizeof(stCollectMsg)) { //数量时把数据插入数据库 if (current_count BATCH_SIZE) { InsertData(conn, sqldata, current_count); current_count 0; } } } // 处理剩余数据 if (current_count 0) { InsertData(conn, sqldata, current_count); } } // 数据入库函数 void InsertData(MYSQL* conn, stCollectMsg* data, int count) { std::string query INSERT INTO collect_data (id, name, value, timestamp) VALUES ; for (int i 0; i count; i) { query ( std::to_string(data[i].id) , data[i].name , std::to_string(data[i].value) , data[i].timestamp ); if (i ! count - 1) query ,; } if (mysql_query(conn, query.c_str())) { std::cerr 入库失败: mysql_error(conn) std::endl; } }组装多条数据一次入库比一条一条入库效率快很多类似的场景很多。堆的应用静态数组能做的申请堆内存的动态数组也能做区别是堆上申请的内存是不连续的导致整体性能没有动态数组和静态数组效率快。但是堆更灵活也更大适合大对象数据以及规模大的数据量处理。比如视频类数据都是以G为单位最好放在动态内存中。虚拟地址空间使用mmap进行内存映射时用户空间会创建虚拟地址区域同时与物理内存建立映射关系应用进程以访问虚拟地址的方式来访问真实的物理内存。以下通过与动态内存对比来加深了解声明以下表格来源于AI辅助创作对比维度mmap内存映射动态内存分配(malloc/free)工作原理将文件/设备映射到进程虚拟地址空间建立直接访问通道通过系统调用从堆(heap)分配内存块返回可操作指针内存来源文件数据或匿名映射区(不关联文件)堆内存空间系统开销建立映射开销较大(需修改页表)适合长期使用的内存区域分配/释放操作轻量适合频繁变动的内存需求管理机制由操作系统内核管理映射关系自动处理页错误需程序员手动管理分配/释放依赖内存管理器实现(如ptmalloc、jemalloc)性能特点• 大文件访问减少拷贝开销• 缺页中断影响随机访问性能• 小内存分配效率高• 频繁分配/释放可能产生碎片内存限制受虚拟地址空间限制(32位系统约3GB用户空间)受物理内存swap空间限制线程安全映射区域自动共享需额外同步机制分配器本身线程安全但使用需加锁典型问题• 内存映射对齐要求(通常4KB)• msync同步策略选择• 内存泄漏风险• use-after-free漏洞• 碎片化导致分配失败优势场景• 大文件随机访问(数据库索引)• 进程间共享内存• 零拷贝数据传输• 灵活分配任意大小内存• 实时内存需求处理• 短生命周期对象适用场景• 数据库引擎• 大型媒体文件处理• 进程间通信• 动态数据结构• 临时缓冲区• 运行时确定大小的对象总结mmap在内存映射时开销比较大处理小数量的内存/数据性能较低其优势场景是适合大文件读写以及多个进程共享一段内存。比如一段高清监控视频可以结合共享内存内存映射提供查看进程、AI分析进程、质量分析进程等多个进程同时访问这种设计方式比每个进程自己加载一段高清监控视频到自己的动态区域后再进行分析的设计方式节省内存以及节省IO操作的开销。以下给出内存映射与常规IO操作对比表声明以下表格来源于AI辅助创作对比维度mmap操作read/write操作工作原理将文件直接映射到进程地址空间通过内核缓冲区进行数据中转优势场景• 随机访问大文件数据库/B树• 频繁访问相同文件区域• 进程间共享内存• 顺序读写日志文件• 流式处理视频编码• 精确控制I/O时序性能表现• 大文件随机访问快3-5倍• 零拷贝网络传输优势• 小文件操作快20-30%• 流式数据吞吐量稳定内存影响可能引发大量缺页中断需预加载策略内核缓冲区可控可调节buffer大小数据安全依赖msync同步默认异步写入同步写入保证数据持久化适用文件大小1MB文件效益明显1MB文件效率更优多进程协作天然支持MAP_SHARED需额外IPC机制复杂度需处理页对齐问题4KB倍数编程模型简单直接典型应用• 数据库引擎• 机器学习模型加载• 地理空间数据处理• 日志收集系统• 压缩包处理• 网络协议栈实现系统限制受限于虚拟内存空间32位系统最大2-3GB仅受物理存储限制