[精通C语言]变量前缀和存储位置的关系

2030阅读 0评论2013-08-03 摩斯电码
分类:C/C++

进程中内存空间的划分:

1.       代码区 存放代码/函数,只读区

2.       全局区 保存全局变量,读写区

3.       BSS未初始化的全局变量,BSS段在main执行前会自动清0

4.       栈区 存局部变量,包括函数形参,栈区的内存是自动分配自动回收的

5.       堆区 程序员自己管理的区域,malloc/ free操作的都是堆区。

6.       只读常量区 存放字符串常量和const修饰的全局变量。

注:只读常量区和代码区合并在一起。

 

上面的这种划分在很多文档中都有看到,这里我不想再一一介绍。只想说一下其中的误解和陷阱,本文讲以一下几部分展开:

1)变量前缀和储存位置

2)为什么要有bss段,全局变量未初始化到底放哪?

3linux察看内存分配的map文件

 

一,变量前缀 和 储存位置:

       这部分帮助大家回顾const, static,auto,全局,局部等变量常量的储存位置。

首先看下面的一个小程序:

const int i2 = 10;

int * p2 = &i2;

*p2 = 200;

printf("%d\n",i2);

大家猜猜输出的结果。是不是会出现编译错误或者运行时错误?

       答案是:输出200。运行时有警告:非常指针指向常变量。

再看下面的一段代码:

  1 #include                                                          

  2 int main()

  3 {

  4     char *a="asdaasasda";

  5     char *p = a;

  6     *p = 'z';

  7     printf("%s\n", a);

  8     return 0;

  9 }

运行时会提示:段保护错误。

这说明局部的const常量并不是保存在常量区,而是和一般的可变常量一样,保存在栈区。只是通过编译阶段错误机制防止变量内容被改变,运行时无法保证,并且函数退出被释放。

再来看static,参考文章开头给的分区机制:

static变 量肯定不能在栈区,因为生存周期是整个文件,所以肯定在全局变量区。而全局变量区和只读常量区就成了所有生存周期贯穿文件的变量的存放位置。那么,第二段 代码中的字符串存在只读常量区,也就是说整个生存周期贯穿文件,也就是说,在函数外也可以通过指针进行访问。这是我们的猜测,那么真实的情况呢?

  1 #include                                                          

  2 char *f()

 3 {

  4     char *t = "happy!";

  5     return t;

  6 }

  7 int main()

  8 {

  9     char *a="asdaasasda";

 10     //char *p = a;

 11     //*p = 'z';

 12     //printf("%s\n", a);

 13     printf("%s\n",f());

 14     return 0;

 15 }

执行结果:happy

 

这样我们可以得出结论:多种声明方式容易搞混,而其实只有生存周期和存储位置的对应关系是不变的。长周期变量:必然存在全局区和常量区,而临时变量都存在栈区。反之亦然:

存在全局区的和常量区的都是长周期变量,栈区都是临时变量。

隐式声明:char *str = "XXXX";其实是static const的。或者可以理解在编译阶段字符串被抽取出来随文件一起保存。

 

二,可爱的BSS段:

BSS段的全称是什么?很多人知道bss段是存储未初始化全局变量的,可是没有去想为什么系统要单独开辟一个区去存未初始化全局变量?bss区有什么特殊的地方,使得他适合去存储未初始化的全局变量?下面为大家解决这些问题:

BSS(Block Started by Symbol),如果大家对汇编有所了解,肯定知道block的概念。我们在C代码中定义的变量都是按照block存的。数组名就是block首地址的symbol。一般都是这样的格式:

AAA       dw 0,1,2.....

AAA后面没有冒号,在运行时如果用AAA做相对寻址,其单元大小都是dw。这就是汇编里对应数组的结构:block

BSS段中有很多数组?对还是错?其实by symbol就是告诉我们有很多数组symbol,但是后面的具体数据并不在bss中。那么这就像一张列出数组symbols的表格了。那么为什么要搞这么一张表,不能直接存数据么?你可以想象如果数组很大,怎么把这么多数据写在二进制文件中。那将会使得文件很大。最关键的是,一会我们会看到,开始分配4G内存的时候,全局变量区的大小是定死了的!你放太大的数组会使得出现多个内存数据不连续片断。而局部变量不存在这个问题,栈是向上增长的,理论上没有容量上限。参看下图:


那么BSS里存着symbolsymbol对应的数据存在哪里?堆!对,书上总是说,在程序加载时,未初始化的全局变量会被初始化为0,其实,这些变量内容不存放在二进制可运行文件中,而是在程序启动时动态申请的,在动态申请时清0。 说到这里发泄一下本人的一点苦闷:前段时间找工作,因为毕业一年以来一直在香港做科研,技术的东西拉掉不少,最近回内地找工作很是苦闷,有个叫深软的小公 司作嵌入式,面试这个公司的时候里面有道题说全局变量存在哪?我选的是堆,正确答案是全局区。就为这道题和面试官吵了起来,最后把出题的一并找来。因为面 试的妹子根本不懂技术,还各种打击我,言外之意要我参加他们那的培训,其实他们就是打着招人的幌子,实质是培训收费。所以找工作的亲们切忌小心,不要上 当。所以说全局变量到底存哪很难说,初始化的存在全局区,未初始化的存在堆中,未初始化的索引名字存在bss中。而我们考虑的主要是未始化的全局数组,所以说存在堆中是比较合适的。本科搞acm的时候学长就跟我说全局大数组存在堆中,谁知道这么一个简单的结论背后藏着这么多秘密。

      

三,linux内存管理文件map

下面为喜欢钻研的朋友介绍一下linux下察看进程中内存分配的方法:cat 对应进程的map文件。在linux中所有的功能都是以文件实现。进程也是如此。每个进程都对应一个文件。这个文件很特殊,没有大小,准确说是不占用硬盘空间。我们可以在/proc中看到。文件名为对应的进程号。

我启动一个死循环程序,然后键入ps察看进程号:



然后cat maps


可以看到4G虚拟内存的分配。

上一篇:没有了
下一篇:Linux进程通信共享内存:shmget、shmat、shmdt、shmctl