资讯详情

linux系统编程(十七):内存管理基础

📅 2026/10/27 10:17:57 | 华诺云谱 👁 阅读
linux系统编程(十七):内存管理基础
内存管理基础 —— 进程内存布局与 malloc 全家桶C 程序员吃过最多的亏都是内存栈溢出、堆泄漏、free 后再用、double free、悬空指针。这篇按进程内存布局 → 栈 vs 堆 → malloc/free 怎么工作 → 常见内存错误 → 内存分配器选型五步讲清楚读完后再写 C对每一个变量 / 每一次 malloc 心里有数。0. 引言这个 bug 在哪char*get_name(void){charbuf[64];strcpy(buf,alice);returnbuf;// ⛔ 返回栈上指针}intmain(void){char*nameget_name();printf(%s\n,name);// 可能打印对可能崩看运气return0;}类似的还有char*pmalloc(100);free(p);p[0]X;// ⛔ use-after-freechar*qmalloc(100);free(q);free(q);// ⛔ double free这些坑都和内存到底在哪有关。下面把 Linux 进程内存布局拆开看。1. 进程内存布局一个 C 程序运行时进程地址空间从低到高大致各区对应结合 19 篇 ELF区对应 ELF 节谁分配谁释放.text.textOS 加载进程退出.rodata.rodataOS 加载进程退出.data.dataOS 加载并初始化进程退出.bss.bssOS 加载并清零进程退出Heap—malloc / brk / mmapfree / munmapStack—编译器自动函数返回自动共享库 mmap各 .so 段ld.so进程退出2. 栈Stack2.1 行为特性说明每个线程一个栈pthread 默认 8MB详见第 1 篇函数调用压栈帧帧内含参数、返回地址、局部变量函数返回弹栈帧自动恢复 SP局部变量随之失效栈向下生长高地址 → 低地址2.2 局部变量在栈上voidf(intn){intlocal_a10;// 栈charbuf[1024];// 栈structpointp{1,2};// 栈}// f 返回后local_a / buf / p 全部失效2.3 栈的优势优势说明零开销分配编译期算好偏移函数入口一次性SP - N完成自动释放函数返回SP N无需手动free缓存友好连续地址CPU cache 命中率高2.4 栈的局限局限说明生命周期受限函数返回就没了大小有限默认 8MB超了SIGSEGV栈溢出递归危险每次调用一帧深递归爆栈2.5 栈大小调整# 查看ulimit-s# 8192 (KB)# 临时改ulimit-s16384# 16MB# 永久改 /etc/security/limits.conf线程级也能在 pthread_create 前设置pthread_attr_tattr;pthread_attr_init(attr);pthread_attr_setstacksize(attr,16*1024*1024);// 16MBpthread_create(tid,attr,fn,NULL);2.6 栈溢出表现voidrecur(intn){charbig[1024];recur(n1);}intmain(void){recur(0);}// Segmentation fault (core dumped)或者大局部数组voidf(void){charbuf[10*1024*1024];// 10MB 8MB → 栈溢出}经验法则单函数局部变量总和 ≤ 几 KB大数据用 malloc 走堆。3. 堆Heap3.1 malloc / free / realloc / calloc#includestdlib.hvoid*malloc(size_tsize);// 分配未初始化void*calloc(size_tnmemb,size_tsize);// 分配 清零void*realloc(void*ptr,size_tsize);// 重新分配voidfree(void*ptr);// 释放int*arrmalloc(100*sizeof(int));if(!arr){/* 失败处理 */}arr[0]1;free(arr);// 清零版适合数组初始化int*zarrcalloc(100,sizeof(int));// 全 0// 扩容arrrealloc(arr,200*sizeof(int));// 可能换新地址3.2 必背规则规则说明malloc必检查返回值内存不足返回 NULLfree(NULL)安全标准保证什么都不做同一指针只free一次重复 free → double free 崩free后立即ptr NULL防 use-after-freerealloc(p, 0)行为依实现可能返回 NULL可能等于 freerealloc失败原指针仍有效旧块不丢3.3 malloc 内部 —— brk / mmap 双轨malloc不直接调系统调用——每次malloc(8)都不会去内核要内存有内存池。底层实际用块大小分配方式说明小块默认 ≤ 128KBbrk()调整堆顶heap 区从.bss之上往上长大块 128KB直接mmap一段匿名内存独立映射不进堆用户层free把内存还给 malloc 池不一定立即归还给 OS。所以top显示进程 RSS 居高不下但 valgrind 又没找到泄漏 ——碎片不归还给 OS。3.4 看 malloc 行为#includemalloc.hmalloc_stats();// 把统计信息打到 stderr或者mallinfo2()拿到结构体编程使用。3.5 阈值控制#includemalloc.hmallopt(M_MMAP_THRESHOLD,64*1024);// 改成 ≥64KB 走 mmapmallopt(M_TRIM_THRESHOLD,128*1024);// free 后超过这值才 trim 还 OS4. 常见内存错误4.1 内存泄漏Memory Leakvoid*pmalloc(100);// 忘记 freereturn;少量泄漏看不出来长跑服务 RSS 持续上涨 → OOM。抓泄漏valgrind / AddressSanitizer / jemalloc heap profile。详见 29 篇。4.2 use-after-freechar*pmalloc(100);free(p);strcpy(p,hi);// ⛔ p 已经无效最阴险的 bug有时正常有时崩因为内存可能被 malloc 重用。防御free(p);pNULL;// 立即置空// 之后 p[0] 访问 → SIGSEGV立刻发现4.3 double freechar*pmalloc(100);free(p);free(p);// ⛔ 崩或破坏堆// glibc 报: free(): double free detected4.4 悬空指针Dangling Pointerchar*create(void){charbuf[100];// 栈strcpy(buf,hi);returnbuf;// ⛔ 返回时 buf 已失效}或者char*pmalloc(100);char*aliasp;free(p);strcpy(alias,hi);// ⛔ alias 也是悬空4.5 堆缓冲区溢出char*pmalloc(10);strcpy(p,this is a long string);// ⛔ 溢出破坏堆元数据下次 malloc 可能崩。4.6 栈缓冲区溢出voidf(constchar*input){charbuf[10];strcpy(buf,input);// ⛔ input 太长 → 栈溢出可能改返回地址}经典安全漏洞。Linux 现代防御栈金丝雀canary NX bit ASLR。4.7 未初始化读取charbuf[100];printf(%s\n,buf);// ⛔ buf 是栈上垃圾值或char*pmalloc(100);// malloc 不清零printf(%s\n,p);// ⛔ p 内容是垃圾要清零用calloc或memset(p, 0, 100)。5. malloc 的安全 wrapper工程实践常包一层void*xmalloc(size_tsize){void*pmalloc(size);if(!p){fprintf(stderr,out of memory\n);abort();}returnp;}void*xcalloc(size_tn,size_ts){void*pcalloc(n,s);if(!p)abort();returnp;}void*xrealloc(void*p,size_tsize){void*qrealloc(p,size);if(!qsize)abort();returnq;}理由出发点论据简化代码把出错检查统一做掉主流程代码更干净场景合理大多数程序内存不足就该退出没法优雅恢复行业先例vim、git、tmux 都有自己的xmalloc6. 内存分配器选型glibc 默认是 ptmalloc2多线程场景碎片严重。生产服务常换分配器特点用法ptmalloc2glibc 默认通用默认jemallocFacebook 出品多线程友好LD_PRELOAD/usr/lib/libjemalloc.so ./progtcmallocGoogle 出品速度快LD_PRELOAD/usr/lib/libtcmalloc.so ./progmimalloc微软新秀性能极佳LD_PRELOAD/usr/lib/libmimalloc.so ./prog切换很简单# 安装 jemallocsudoaptinstalllibjemalloc-dev# 用 LD_PRELOAD 替换LD_PRELOAD/usr/lib/x86_64-linux-gnu/libjemalloc.so ./myapp或者编译时链接gcc main.c-omyapp-ljemalloc实测高频 malloc/free 的多线程服务换 jemalloc 性能常提升 10-30%内存碎片显著降低。Redis、Nginx、Rust runtime 都支持选择分配器。7. 自己写对象池 / 内存池频繁 malloc 同尺寸对象时对象池比 malloc 快 10-100 倍typedefstructchunk{structchunk*next;chardata[64];}chunk_t;staticchunk_t*freelistNULL;chunk_t*pool_alloc(void){if(freelist){chunk_t*cfreelist;freelistc-next;returnc;}returnmalloc(sizeof(chunk_t));}voidpool_free(chunk_t*c){c-nextfreelist;freelistc;}进阶arena 分配器一次性分配大块像栈一样递增分配typedefstruct{char*base;size_tsize,used;}arena_t;void*arena_alloc(arena_t*a,size_tn){if(a-usedna-size)returnNULL;void*pa-basea-used;a-usedn;returnp;}// 不能单独 free整个 arena 一锅释放适合批量分配 一次性释放场景HTTP 请求处理、编译器 AST 等。Postgres、Apache、nginx 都内置这类 arena。8. 内存对齐C 结构体不是简单字段大小相加structs{chara;// 1intb;// 4要 4 字节对齐charc;// 1};// sizeof(struct s) 12不是 6布局项内容偏移 0a (1)偏移 1-3padding偏移 4b (4)偏移 8c (1)偏移 9-11tail padding (对齐 sizeof 到 4)调整字段顺序节省内存structs2{intb;// 4chara;// 1charc;// 1};// sizeof(struct s2) 8强制不对齐极少用structs__attribute__((packed)){...};// sizeof 6注意packed 结构访问慢CPU 不喜欢未对齐跨平台未必兼容。详见 20 篇 C 语言常见坑。9. valgrind 抓内存错误终极武器valgrind --toolmemcheck --leak-checkfull ./myapp10. 总结进程内存布局速记API 速记API用途malloc(n)分配 n 字节未初始化calloc(n, sz)分配 清零realloc(p, n)改大小free(p)释放free(NULL)安全避坑口诀malloc必检查返回值free 后立即置 NULL同一指针只 free 一次局部 char 数组别返回大对象走堆不要堆栈malloc memset 就是 calloc怀疑内存问题valgrind / ASan金句内存是 C 程序的命门搞懂在哪 / 谁分配 / 谁释放三件事半数 bug 不会出现。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑