Chinaunix首页 | 论坛 | 博客
  • 博客访问: 525332
  • 博文数量: 118
  • 博客积分: 10028
  • 博客等级: 上将
  • 技术积分: 1820
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-07 18:46
文章分类

全部博文(118)

文章存档

2009年(12)

2008年(106)

我的朋友

分类: C/C++

2008-12-02 14:03:30


因为前次笔试考了这个,但是网上都没找到比较全的解答,今天有幸看到了,所以转过来先。。。

==============================================================

初学者对内存和指针只有一知半解的认识,常常犯一些连自己都莫名其妙的错误。为了避免这些不必要的错误,今天我们要学习各种数据存放的位置以及它们的特性,让初学者对编程有更进一步的认识。在程序中,数据存放的位置主要有以下几个:

1.未初始化的全局变量(.bss段)

已经记不清bss代表Block Storage Start还是Block Started by Symbol。像我这种没有用过那些史前计算机的人,终究无法明白这样怪异的名字,记不住也是不足为奇的。不过没有关系,重要的是,我们要清楚什么数据是 存放在bss段中的,这些数据有什么样的特点以及如何使用它们。

通俗的说,bss段是用来存放那些没有初始化的和初始化为0的全局变量的。它有什么特点呢,让我们来看看一个小程序的表现。

int bss_array[1024 * 1024];

int main(int argc, char* argv[])
{
return 0;
}
# gcc -g bss.c -o bss.exe
# ls -l bss.exe
-rwxrwxr-x 1 root root 5975 Nov 16 09:32 bss.exe
# objdump -h bss.exe |grep bss
24 .bss 00400020 080495e0 080495e0 000005e0 2**5

变量bss_array的大小为4M,而可执行文件的大小只有5K。 由此可见,bss类型的全局变量只占运行时的内存空间,而不占用文件空间。

现代大多数操作系统,在加载程序时,会把所有的bss全局变量清零。但为保证程序的可移植性,手工把这些变量初始化为0也是一个好习惯,这样这些变量都有个确定的初始值。

当然作为全局变量,在整个程序的运行周期内,bss数据是一直存在的。

2.初始化过的全局变量 (.data段)

与bss相比,data段就容易明白多了,它的名字就暗示着里面存放着数据。当然,如果数据全是零,为了优化考虑,编译器把它当作bss处理。通俗的说,data段用来存放那些初始化为非零的全局变量。它有什么特点呢,我们还是来看看一个小程序的表现。

int data_array[1024 * 1024] = {1};

int main(int argc, char* argv[])
{
return 0;
}
# ls -l data.exe
-rwxrwxr-x 1 root root 4200313 Nov 16 09:34 data.exe
# objdump -h data.exe |grep \\.data
23 .data 00400020 080495e0 080495e0 000005e0 2**5

仅仅是把初始化的值改为非零了,文件就变为4M多。由此可见,data类型的全局变量是即占文件空间,又占用运行时内存空间的。

同样作为全局变量,在整个程序的运行周期内,data数据是一直存在的。

3.常量数据 (.rodata段)

rodata的意义同样明显,ro代表read only,rodata就是用来存放常量数据的。关于rodata类型的数据,要注意以下几点:

o 常量不一定就放在rodata里,有的立即数直接和指令编码在一起,存放在代码段(.text)中。

o 对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。

o rodata是在多个进程间是共享的,这样可以提高运行空间利用率。

o 在有的嵌入式系统中,rodata放在ROM(或者norflash)里,运行时直接读取,无需加载到RAM内存中。

o 在嵌入式linux系统中,也可以通过一种叫作XIP(就地执行)的技术,也可以直接读取,而无需加载到RAM内存中。

o 常量是不能修改的,修改常量在linux下会出现段错误。

由此可见,把在运行过程中不会改变的数据设为rodata类型的是有好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同 时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以提高程序的稳定性。

字符串会被编译器自动放到rodata中,其它数据要放到rodata中,只需要加const关键字修饰就好了。

4.代码 (.text段)

text段存放代码(如函数)和部分整数常量,它与rodata段很相似,相同的特性我们就不重复了,主要不同在于这个段是可以执行的

5. 栈(stack)

栈用于存放临时变量和函数参数。栈作为一种基本数据结构,我并不感到惊讶,用来实现函数调用,这也司空见惯的作法。直到我试图找到另外一种方式实现递归操作时,我才感叹于它的巧妙。要实现递归操作,不用栈不是不可能,只是找不出比它更优雅的方式。

尽管大多数编译器在优化时,会把常用的参数或者局部变量放入寄存器中。但用栈来管理函数调用时的临时变量(局部变量和参数)是通用做法,前者只是辅助手段,且只在当前函数中使用,一旦调用下一层函数,这些值仍然要存入栈中才行。

通常情况下,栈向下(低地址)增长,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退。一个有兴趣的问 题:在x86平台上,栈顶寄存器为ESP,那么ESP的值在是PUSH操作之前修改呢,还是在PUSH操作之后修改呢?PUSH ESP这条指令会向栈中存入什么数据呢?据说x86系列CPU中,除了286外,都是先修改ESP,再压栈的。由于286没有CPUID指令,有的OS用 这种方法检查286的型号。

要注意的是,存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也自动释放了,继续访问这些变量会造成意想不到的错误。

6.堆(heap)

堆是最灵活的一种内存,它的生命周期完全由使用者控制。标准C提供几个函数:

malloc 用来分配一块指定大小的内存。
realloc 用来调整/重分配一块存在的内存。
free 用来释放不再使用的内存。

使用堆内存时请注意两个问题:

alloc/free要配对使用。内存分配了不释放我们称为内存泄露(memory leak),内存泄露多了迟早会出现Out of memory的错误,再分配内存就会失败。当然释放时也只能释放分配出来的内存,释放无效的内存,或者重复free都是不行的,会造成程序crash。

分配多少用多少。分配了100字节就只能用100字节,不管是读还是写,都只能在这个范围内,读多了会读到随机的数据,写多了会造成的随机的破坏。这种情况我们称为缓冲区溢出(buffer overflow),这是非常严重的,大部分安全问题都是由缓冲区溢出引起的。

手工检查有没有内存泄露或者缓冲区溢出是很困难的,幸好有些工具可以使用,比如linux下有valgrind,它的使用方法很简单,大家下去可以试用一下,以后每次写完程序都应该用valgrind跑一遍。

最后,我们来看看在linux下,程序运行时空间的分配情况:

# cat /proc/self/maps 

00110000-00111000 r-xp 00110000 00:00 0 [vdso]
009ba000-009d6000 r-xp 00000000 08:01 768759 /lib/ld-2.8.so
009d6000-009d7000 r--p 0001c000 08:01 768759 /lib/ld-2.8.so
009d7000-009d8000 rw-p 0001d000 08:01 768759 /lib/ld-2.8.so
009da000-00b3d000 r-xp 00000000 08:01 768760 /lib/libc-2.8.so
00b3d000-00b3f000 r--p 00163000 08:01 768760 /lib/libc-2.8.so
00b3f000-00b40000 rw-p 00165000 08:01 768760 /lib/libc-2.8.so
00b40000-00b43000 rw-p 00b40000 00:00 0
08048000-08050000 r-xp 00000000 08:01 993652 /bin/cat
08050000-08051000 rw-p 00007000 08:01 993652 /bin/cat
0805f000-08080000 rw-p 0805f000 00:00 0 [heap]
b7fe8000-b7fea000 rw-p b7fe8000 00:00 0
bfee7000-bfefc000 rw-p bffeb000 00:00 0 [stack]

每个区间都有四个属性:

r 表示可以读取。
w 表示可以修改。
x 表示可以执行。
p/s 表示是否为共享内存。

有文件名的内存区间,属性为r—p表示存放的是rodata。
有文件名的内存区间,属性为rw-p表示存放的是bss和data
有文件名的内存区间,属性为r-xp表示存放的是text数据。
没有文件名的内存区间,表示用mmap映射的匿名空间。
文件名为[stack]的内存区间表示是栈。
文件名为[heap]的内存区间表示是堆。

转自:http://blog.csdn.net/absurd/archive/2008/11/25/3367421.aspx
阅读(1368) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~