各位看官们,大家好,上一回中咱们说的是C程序内存布局的例子,这一回咱们继续说该例子。闲话休提,言归正转。让我们一起talk C栗子吧!
看官们,我们在上一回中介绍了C程序在内存中的布局,并且给大家做了简单的演示。上一回的例子比较简单,只能说明程序中内存布局的大体轮廓。我们今天会通过具体的内存地址来清楚地介绍C程序在内存中的布局。下面是例子的程序源代码,请大家参考:
int ga1;
int ga2 = 1;
int func()
{
int i = 0;
static static_la1;
static static_la2 = 3;
printf("func is running \n");
printf("Address of i: %p \n",&i);
printf("Address of static_la1 : %p \n",&static_la1);
printf("Address of static_la2 : %p \n",&static_la2);
while(i++<8)
sleep(1);
return 0;
}
int main()
{
int la1;
int la2 = 2;
int *p;
p = (int *) malloc(3*sizeof(int));
if(NULL == p)
printf("malloc failed \n");
printf("Address of ga1: %p \n",&ga1);
printf("Address of ga2: %p \n",&ga2);
printf("Address of la1: %p \n",&la1);
printf("Address of la2: %p \n",&la2);
printf("Address of p: %p \n",p);
func();
free(p);
p = NULL;
return 0;
}
在该代码中,我们手动输出各个变量的地址,这样做的目的是为了通过变量的地址来判断变量在内存中的位置,进而确认变量属于内存布局中的哪个分区。那么这些变量究竟是在哪个分区中呢?data分区还是bss分区?下面是程序的运行结果,请大家参考:
Address of ga1: 0x804a040
Address of ga2: 0x804a030
Address of la1: 0xbfc7e7b4
Address of la2: 0xbfc7e7b8
Address of p: 0x8ffc008
func is running
Address of i: 0xbfc7e78c
Address of static_la1 : 0x804a03c
Address of static_la2 : 0x804a034
从程序的运行结果中我们可以看到,各个变量在内存中的地址,不过,我们还是不知道这些变量属于哪个内存分区。看官们莫急,我们通过readelf工具来查看该程序的内存布局,通过内存布局就可以看到各个内存分区的地址范围,这些我就们就能依据变量的地址推断出变量所在的内存分区。下面是详细的结果:
readelf -S s //使用readelf 工具查看程序的内存布局
There are 30 section headers, starting at offset 0x1190:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc 000090 10 A 6 1 4
[ 6] .dynstr STRTAB 0804825c 00025c 000063 00 A 0 0 1
[ 7] .gnu.version VERSYM 080482c0 0002c0 000012 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080482d4 0002d4 000020 00 A 6 1 4
[ 9] .rel.dyn REL 080482f4 0002f4 000008 08 A 5 0 4
[10] .rel.plt REL 080482fc 0002fc 000038 08 A 5 12 4
[11] .init PROGBITS 08048334 000334 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048360 000360 000080 04 AX 0 0 16
[13] .text PROGBITS 080483e0 0003e0 0002a2 00 AX 0 0 16
[14] .fini PROGBITS 08048684 000684 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048698 000698 0000dc 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 08048774 000774 000034 00 A 0 0 4
[17] .eh_frame PROGBITS 080487a8 0007a8 0000d0 00 A 0 0 4
[18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 000028 04 WA 0 0 4
[24] .data PROGBITS 0804a028 001028 000010 00 WA 0 0 4
[25] .bss NOBITS 0804a038 001038 00000c 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001038 00004f 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 001087 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 001640 0004c0 10 29 47 4
[29] .strtab STRTAB 00000000 001b00 0002be 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
从运行结果中可以看到程序的
data分区从地址0x0804a028开始,大小为0x000010(16byte)。
bss分区从0x0804a038开始,大小为0x00000c(12byte)。
那么我们结合程序中变量的地址来推断一下变量所在的内存分区:
Address of ga1: 0x804a040 //变量的地址大于bss(0x0804a038),变量位于bss分区
Address of ga2: 0x804a030 //变量的地址大于data(0x0804a028),而且小于bss,变量属于data分区
Address of static_la1 : 0x804a03c //变量的地址大于bss(0x0804a038),变量位于bss分区
Address of static_la2 : 0x804a034 //变量的地址大于data(0x0804a028),而且小于bss,变量属于data分区
还有四个变量的地址不在bss和data分区范围内,因此不能确定这些变量所在的分区:
Address of la1: 0xbfc7e7b4 //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of la2: 0xbfc7e7b8 //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of p: 0x8ffc008 //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
Address of i: 0xbfc7e78c //变量的地址大于bss(0x0804a038),但是超出了bss分区的范围
我们在前一章回中介绍过,重要的分区就四种,他们不在data和bss,那么只能在堆区或者栈区了。现在我们只有变量的地址,要是有堆区或者栈区的地址范围就好了。这样我们就可以向刚才推断bss和data分区中的变量一样来推断这些变量在内存中的分区。
接下我们一起查找一下程序的栈和堆分区。还记得我们在前面章回中介绍的proc虚拟文件系统吗?我们可以借助它来获得栈区和堆区的内存空间信息。
./s & //在后台运行编译好的程序
[1] 2736 //程序在后台正常运行,同时显示程序的PID
Address of ga1: 0x804a040 //程序运行,输出程序中的内容
Address of ga2: 0x804a030
Address of la1: 0xbfe01cf4
Address of la2: 0xbfe01cf8
Address of p: 0x964d008
func is running
Address of i: 0xbfe01ccc
Address of static_la1 : 0x804a03c
Address of static_la2 : 0x804a034
cat /proc/2736/maps //查看proc中关于进程的信息
08048000-08049000 r-xp 00000000 08:13 22415696 /home/talk8/s
08049000-0804a000 r--p 00000000 08:13 22415696 /home/talk8/s
0804a000-0804b000 rw-p 00001000 08:13 22415696 /home/talk8/s
0964d000-0966e000 rw-p 00000000 00:00 0 [heap]
b75b7000-b75b8000 rw-p 00000000 00:00 0
b75b8000-b7761000 r-xp 00000000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7761000-b7763000 r--p 001a9000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7763000-b7764000 rw-p 001ab000 08:16 6444 /lib/i386-linux-gnu/libc-2.19.so
b7764000-b7767000 rw-p 00000000 00:00 0
b777e000-b7781000 rw-p 00000000 00:00 0
b7781000-b7782000 r-xp 00000000 00:00 0 [vdso]
b7782000-b77a2000 r-xp 00000000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
b77a2000-b77a3000 r--p 0001f000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
b77a3000-b77a4000 rw-p 00020000 08:16 6459 /lib/i386-linux-gnu/ld-2.19.so
bfde3000-bfe04000 rw-p 00000000 00:00 0 [stack]
从上面信息中我们可以看到,标记为[heap]和[stack]的分区就是我们要找的堆区和栈区,与其在同一行中的内容显示了这两个分区的内存地址空间。
堆区:0x0964d000-0x0966e000
栈区:0xbfde3000-0xbfe04000
现在,我们可以推断刚才哪四个不知道自己所在分区的变量了,下面是我们的推断结果:
Address of la1: 0xbfe01cf4 //变量的地址位于栈区地址空间内,该变量位于栈区
Address of la2: 0xbfe01cf8 //变量的地址位于栈区地址空间内,该变量位于栈区
Address of p: 0x964d008 //变量的地址位于堆区地址空间内,该变量位于堆区
Address of i: 0xbfe01ccc //变量的地址位于栈区地址空间内,该变量位于栈区
看官们,到目前为止, 我们通过具体的内存地址清楚地展示了C程序在内存中的布局。最后我们画一个直观的图形给大家看,这样大家就能直观地看清楚C程序的内存模型了。
该图形比较简单,在图形中从上向下看,可以看到内存地址从高地址向低地址延伸,每个地址后面是与该地址对应的分区名称。
看官们,完整的代码放到了我的资源中,大家可以点击这里下载使用(CSDN又出问题了,所以暂时不能上传程序,等CSDN修复好问题后,我会及时上传程序,到时候大家可以到我们资源中下载程序)。因为每个人的电脑是不同的,而且所使用的编程环境也可能不相同,所以建议各位看官都下载程序到自己的电脑上运行,然后按照我们介绍的方法观察程序在内存中的模型。
各位看官,关于C程序内存布局的例子咱们就说到这里。欲知后面还有什么例子,且听下回分解 。
一起talk C栗子吧(第一百三十回:C语言实例--C程序内存布局二)
原文:http://blog.csdn.net/talk_8/article/details/50993055