没有合适的资源?快使用搜索试试~ 我知道了~
大内高手(详细的内存知识)
5星 · 超过95%的资源 需积分: 16 18 下载量 145 浏览量
2008-09-10
20:59:18
上传
评论
收藏 765KB DOC 举报
温馨提示
试读
43页
大内高手,介绍内存相关的知识 我一直认为作为一个在linux下工作的C程序员,若对内存有深刻的认识,不但程序的性能会更高,运行更稳定,编程速度也会更快。反之亦有相反的效果,有时一些内存错误让你摸不着头脑,不但大大降低开发速度,开发出来的软件稳定性也值得怀疑。 为了提高组员的编程水平,去年我制定了一系列的培训计划,并负责主讲部分重要课程,其中基础类课程中,有一堂关于内存的课程。当时的讲稿写得很粗略,后来的新同事说看不明白,我决定重新整理一下,放在BLOG,希望对新手有所帮助。 至于文章的标题,基本上是为了搞笑。最近重温星爷经典《大内密探零零发》,这是以大内高手作为文章的标题原因之一。另外也可以这样理解,内指内存,至于大字,修饰内存指其容量大,修饰内存高手形容水平很高,两种理解都可以。
资源推荐
资源详情
资源评论
我一直认为作为一个在 linux 下工作的 C 程序员,若对内存有深刻的认识,
不但程序的性能会更高,运行更稳定,编程速度也会更快。反之亦有相反的效
果,有时一些内存错误让你摸不着头脑,不但大大降低开发速度,开发出来的
软件稳定性也值得怀疑。
为了提高组员的编程水平,去年我制定了一系列的培训计划,并负责主讲
部分重要课程,其中基础类课程中,有一堂关于内存的课程。当时的讲稿写得
很粗略,后来的新同事说看不明白,我决定重新整理一下,放在 BLOG,希望
对新手有所帮助。
至于文章的标题,基本上是为了搞笑。最近重温星爷经典《大内密探零零
发》,这是以大内高手作为文章的标题原因之一。另外也可以这样理解,内指
内存,至于大字,修饰内存指其容量大,修饰内存高手形容水平很高,两种理
解都可以。
其中包括下列文章:
1. 大内高手—内存模型
单线程模型
多线程模型
2. 大内高手—栈/堆
backtrace 的实现
alloca 的实现
可变参数的实现。
malloc/free 系列函数简介
new/delete 系列操作符简介
3. 大内高手—全局内存
.bss 说明
.data 说明
.rodata 说明
violatile 关键字说明
static 关键字说明
const 关键字说明
4. 大内高手—内存分配算法
标准 C(glibc)分配算法
STL(STLPort)分配算法
OS 内部分配算法(伙伴/SLAB)
5. 大内高手—惯用手法
引用计数
预先分配
内存池
会话池
6. 大内高手—共享内存与线程局部存储
7. 大内高手—自动内存回收机制
8. 大内高手—常见内存错误
9. 大内高手—常用调试工具
1 内存模型
了解 linux 的内存模型,或许不能让你大幅度提高编程能力,但是作为一个
基本知识点应该熟悉。坐火车外出旅行时,即时你对沿途的地方一无所知,仍
然可以到达目标地。但是你对整个路途都很比较清楚的话,每到一个站都知道
自己在哪里,知道当地的风土人情,对比一下所见所想,旅程可能更有趣一些。
类似的,了解 linux 的内存模型,你知道每块内存,每个变量,在系统中处
于什么样的位置。这同样会让你心情愉快,知道这些,有时还会让你的生活轻
更松些。看看变量的地址,你可以大致断定这是否是一个有效的地址。一个变
量被破坏了,你可以大致推断谁是犯罪嫌疑人。
Linux 的内存模型,一般为:
地址 作用 说明
>=0xc000 0000
内核虚拟存储器 用户代码不可见区
域
<0xc000 0000
Stack(用户栈) ESP 指向栈顶
↓
↑
空闲内存
>=0x4000 0000
文件映射区
<0x4000 0000
↑
空闲内存
Heap(运行时堆) 通过 brk/sbrk 系统
调用扩大堆,向上增
长。
.data、.bss(读写段) 从可执行文件中加
载
>=0x0804 8000
.init、.text、.rodata(只
读段)
从可执行文件中加
载
<0x0804 8000
保留区域
很多书上都有类似的描述,本图取自于《深入理解计算机系统》p603,略
做修改。本图比较清析,很容易理解,但仍然有两点不足。下面补充说明一下:
1. 第一点是关于运行时堆的。
为说明这个问题,我们先运行一个测试程序,并观察其结果:
#include <stdio.h>
int main(int argc, char* argv[])
{
int first = 0;
int* p0 = malloc(1024);
int* p1 = malloc(1024 * 1024);
int* p2 = malloc(512 * 1024 * 1024 );
int* p3 = malloc(1024 * 1024 * 1024 );
printf("main=%p print=%p\n", main, printf);
printf("first=%p\n", &first);
printf("p0=%p p1=%p p2=%p p3=%p\n", p0, p1, p2, p3);
getchar();
return 0;
}
运行后,输出结果为:
main=0x8048404 print=0x8048324
first=0xbfcd1264
p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008
main 和 print 两个函数是代码段(.text)的,其地址符合表一的描述。
first 是第一个临时变量,由于在 first 之前还有一些环境变量,它的值并
非 0xbfffffff,而是 0xbfcd1264,这是正常的。
p0 是在堆中分配的,其地址小于 0x4000 0000,这也是正常的。
但 p1 和 p2 也是在堆中分配的,而其地址竟大于 0x4000 0000,与表一描
述不符。
原因在于:运行时堆的位置与内存管理算法相关,也就是与 malloc 的实现
相关。关于内存管理算法的问题,我们在后继文章中有详细描述,这里只作简
要说明。在 glibc 实现的内存管理算法中,Malloc 小块内存是在小于 0x4000
0000 的内存中分配的,通过 brk/sbrk 不断向上扩展,而分配大块内存,malloc
直接通过系统调用 mmap 实现,分配得到的地址在文件映射区,所以其地址大
于 0x4000 0000。
从 maps 文件中可以清楚的看到一点:
00514000-00515000 r-xp 00514000 00:00 0
00624000-0063e000 r-xp 00000000 03:01 718192 /lib/ld-2.3.5.so
0063e000-0063f000 r-xp 00019000 03:01 718192 /lib/ld-2.3.5.so
0063f000-00640000 rwxp 0001a000 03:01 718192 /lib/ld-2.3.5.so
00642000-00766000 r-xp 00000000 03:01 718193 /lib/libc-2.3.5.so
00766000-00768000 r-xp 00124000 03:01 718193 /lib/libc-2.3.5.so
00768000-0076a000 rwxp 00126000 03:01 718193 /lib/libc-2.3.5.so
0076a000-0076c000 rwxp 0076a000 00:00 0
08048000-08049000 r-xp 00000000 03:01 1307138 /root/test/mem/t.exe
08049000-0804a000 rw-p 00000000 03:01 1307138 /root/test/mem/t.exe
09f5d000-09f7e000 rw-p 09f5d000 00:00 0 [heap]
57e2f000-b7f35000 rw-p 57e2f000 00:00 0
b7f44000-b7f45000 rw-p b7f44000 00:00 0
bfb2f000-bfb45000 rw-p bfb2f000 00:00 0 [stack]
2. 第二是关于多线程的。
现在的应用程序,多线程的居多。表一所描述的模型无法适用于多线程环
境。按表一所述,程序最多拥有上 G 的栈空间,事实上,在多线程情况下,能
用的栈空间是非常有限的。为了说明这个问题,我们再看另外一个测试:
#include <stdio.h>
#include <pthread.h>
void* thread_proc(void* param)
{
int first = 0;
int* p0 = malloc(1024);
int* p1 = malloc(1024 * 1024);
printf("(0x%x): first=%p\n", pthread_self(), &first);
printf("(0x%x): p0=%p p1=%p \n", pthread_self(), p0, p1);
return 0;
}
#define N 5
int main(int argc, char* argv[])
{
int first = 0;
int i= 0;
void* ret = NULL;
pthread_t tid[N] = {0};
printf("first=%p\n", &first);
for(i = 0; i < N; i++)
{
pthread_create(tid+i, NULL, thread_proc, NULL);
}
for(i = 0; i < N; i++)
{
pthread_join(tid[i], &ret);
}
return 0;
}
运行后,输出结果为:
first=0xbfd3d35c
(0xb7f2cbb0): first=0xb7f2c454
(0xb7f2cbb0): p0=0x84d52d8 p1=0xb4c27008
(0xb752bbb0): first=0xb752b454
(0xb752bbb0): p0=0x84d56e0 p1=0xb4b26008
(0xb6b2abb0): first=0xb6b2a454
(0xb6b2abb0): p0=0x84d5ae8 p1=0xb4a25008
(0xb6129bb0): first=0xb6129454
(0xb6129bb0): p0=0x84d5ef0 p1=0xb4924008
(0xb5728bb0): first=0xb5728454
(0xb5728bb0): p0=0x84d62f8 p1=0xb7e2c008
我们看一下:
主线程与第一个线程的栈之间的距离:0xbfd3d35c -
0xb7f2c454=0x7e10f08=126M
第一个线程与第二个线程的栈之间的距离:0xb7f2c454 -
0xb752b454=0xa01000=10M
其它几个线程的栈之间距离均为 10M。
也就是说,主线程的栈空间最大为 126M,而普通线程的栈空间仅为
10M,超这个范围就会造成栈溢出。
栈溢出的后果是比较严重的,或者出现 Segmentation fault 错误,或者出现
莫名其妙的错误。
2. 大内高手—栈/堆
栈
栈作为一种基本数据结构,我并不感到惊讶,用来实现函数调用,这也司
空见惯的作法。直到我试图找到另外一种方式实现递归操作时,我才感叹于它
的巧妙。要实现递归操作,不用栈不是不可能,而是找不出比它更优雅的方式。
尽管大多数编译器在优化时,会把常用的参数或者局部变量放入寄存器中。
但用栈来管理函数调用时的临时变量(局部变量和参数)是通用做法,前者只
剩余42页未读,继续阅读
资源评论
- tor_gate2013-06-07很有帮助,内存很重要
- Csm4a12011-09-30这个很好。很实用。
adcxf
- 粉丝: 163
- 资源: 14
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功