做了这么年的linux c开发,经常碰到各种内存问题。这里结合网上资料做一下总结。
一. 内存位置
在C语言中,定义了4个内存区间:代码区;全局变量和静态变量区;局部变量区即栈区;动态存储区,即堆区;
1. bss段 block started by symbol. 存储没有被初始化的全局和静态变量。
2. data段 存储被初始化的全局和静态变量
3. rodata段 常量变量。 read only. 用cons来修饰
4. text段 代码段
5. stack栈 用来储存局部变量,函数的参数值,地址向下增长。
6. heap堆 被programmer控制,由malloc和free获取或释放。
二. 各种内存错误
1. 内存泄漏memory leak 调用malloc后没有free,导致内存剩余容量越来越少。解决方法:
a. 通过少量的实践和适当的文本搜索,您能够快速验证平衡的 malloc() 和 free() 或者 new 和 delete 的源主体
b. 运用实时监测工具, 如valgrind
2. 缓冲区溢出buffer overlow. 在分配的内存外的读写数据
stack overflow: 实际上是一种buffer overflow. 以下是*的描述:
栈有自己的内存大小限制,栈大小由编程语言,机器架构,多线程等在程序开始决定的,当程序尝试使用超过栈空间的部分,就会出现overflow,程序崩溃。
stack overflow常用的导致方式是递归,重复调用自己,导致内存用尽
An example of infinite recursion in C
int foo() { return foo(); }
如果用了-O2/-O3,那么可能会有一些内部的优化使得程序不会有段错误)。一个递归例子:
|
|
int pow(int base, int exp) { if (exp > 0) return base * pow(base, exp - 1); else return 1; } |
int pow(int base, int exp) { return pow_accum(base, exp, 1); }
int pow_accum(int base, int exp, int accum) { if (exp > 0) return pow_accum(base, exp - 1, accum * base); else return accum; } |
运行完结果对比:
pow(5, 4) 5 * pow(5, 3) 5 * (5 * pow(5, 2)) 5 * (5 * (5 * pow(5, 1))) 5 * (5 * (5 * (5 * pow(5, 0)))) 5 * (5 * (5 * (5 * 1))) 625
|
pow(5, 4) pow_accum(5, 4, 1) pow_accum(5, 3, 5) pow_accum(5, 2, 25) pow_accum(5, 1, 125) pow_accum(5, 0, 625) 625
|
左边的代码要保存很多中间变量,一旦exp太大,会发生overflow。右边只需要保存三个变量,不会产生overflow。这样迭代风格代码的转换,只需要把上一次的迭代结果放在下一次的迭代的input里面就可以解决。很有技巧!
另外一个,是因为函数里面的局部变量太大,如:
int foo() { double x[1048576]; }
一个线程时,工作顺利。但是当多个线程时,程序崩溃, 这是因为多线程时stack空间比单线程小。
三. 内存碎片
分配的内存在内存空间内小而不连续。内存分配较小,并且分配的这些小的内存生存周期又较长,反复申请后将产生内存碎片的出现。如何避免:
a. 尽量少使用动态分配内存函数如malloc等。同时分配和释放要在同一函数里面。
b. 一次性分配一个很大的内存,自己做内存池,自己管理内存。
四. segmentation error 段错误。
段错误是指访问的内存超出了系统给的这个程序的内存空间。简单例子:
int a;
int b;
} test;
test *t = NULL;
}
运行完程序报segmentation error. 可以用gdb去debug. 如下图, 在compile的时候,加上-g. 运行时, gdb xxx(运行的程序)进入debug界面,run完之后,在backtrace,查出错的路径。
小窍门: 在-O3优化中发生segmentation error, 复杂代码可能用gdb backtrace 找不到正确的位置,那么我们可以在大概的位置加上
#pragma opt_level = "O0"
强制它变成O0。