静态存储区和堆栈解析

以下均来自网络高手所列举出来的问题,绝非自己写作。只是自己也有不太深入了解的地方。作为笔录,以待深入研究学习!

*******************************************************************************************************************************************************

下面的例子说明指针和数组的区别

 
 
  1. char* p = “Hello World1”;  
  2. char a[] = “Hello World2”;  
  3. p[2] = ‘A’;  
  4. a[2] = ‘A’;  
  5. char* p1 = “Hello World1;” 

这个程序是有错误的,错误发生在p[2] =  ‘A’这行代码处,为什么呢,是变量p和变量数组a都存在于栈区的(任何临时变量都是处于栈区的,包括在main()函数中定义的变量)。

因为数据“Hello  World2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任何问题的。因为指针变量p仅仅能够存储某个存储空间的地址,数据“Hello  World1”为字符串常量,所以存储在静态存储区

虽然通过p[2]可以访问到静态存储区中的第三个数据单元,即字符‘l’所在的存储的单元。但是因为 数据“Hello  World1”为字符串常量,不可以改变,所以在程序运行时,会报告内存错误。如果此时对p和p1输出的时候会发现p和p1里面保存的地址是完全相 同的。换句话说,在数据区只保留一份相同的数据

摘自:http://home.51cto.com/index.php?s=/space/4778867

*******************************************************************************************************************************************************

仔细研究了上面的问题,算是基本理解了。但自己还是没能更深入的解析问题,网上搜索了一下来自作者王春浩静态存储区和堆栈

忠诚的感谢上面两位网友,携手共进步!

呵呵呵,学习了,加油!

 

 

 

静态存储区和堆栈  

介绍:
学习 C ++如果不了解内存分配是一件非常可悲的事情。而且,可以这样讲,一个 C ++程序员无法掌握内存、无法了解内存,是不能够成为一个合格的 C ++程序员的。因此本节主要讲解静态存储区,堆及栈的区别和联系。
 
1基础知识
2静态存储区和堆栈
3堆和栈的比较
  3.1申请方式
  3.2申请后系统的响应
  3.3申请大小的限制
  3.4申请效率的比较
  3.5堆和栈中的存储内容
  3.6存取效率的比较
4总结
 
一  基础知识
程序的内存分配  

  一个由C/C++编译的程序占用的内存分为以下几个部分 

  
1、 静态存储区(全局区)(static)    初始化数据段与非初始化数据段
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 程序结束后由系统释放
 
BSS段:非初始化数据段。 BSS 段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。【定义而没有赋初值的全局变量和静态变量,放在这个区域】


数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。【存放在编译阶段(而非运行时)就能确定的数据,可读可写】

 
 
2、 文字常量区   --------正文段
常量字符串就是放在这里的,程序结束后由系统释放
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。【就是放程序代码的,编译时确定,只读】
 
 

3、栈区(stack)

在经典计算机科学,栈是硬件。主要作用表现为一种数据结构,是只能在某一端插入和删除的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。  
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。   
栈可以用来在函数调用的时候存储断点,保护现场,做递归时要用到栈!(栈的基础知识) 
在计算机系统中, 栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。在i386机器中,栈顶由称为esp的寄存器进行定位。压栈的操作使得栈顶的地址减小,弹出的操作使得栈顶的地址增大。  
栈在程序的运行中有着举足轻重的作用。最重要的是栈保存了一个函数调用时所需要的维护信息,这常常称之为堆栈帧或者活动记录。堆栈帧一般包含如下几方面的信息:  
1.函数的返回地址和参数   
2.临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor  belt)一样,Stack  Pointer会自动指引你到放东西的位置,你所要做的只是把东 西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快,当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的 大小 ,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个" 大小多少"是 在编译时确定的 ,不是在运行时
 
 
 
4、 堆区(heap)  或称为自由存储区
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理 的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据 要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性
事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有 在运行时创建了对象之后才能确定.在C ++ 中,要求创建一个对象时,只需用new命令编制相关的代码即可 执行这些代码时,会在堆里自动进行数据的保存 . 当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间 这也正是导致效率低的原因。
 
什么是常见的堆性能问题以下是使用堆时会遇到的最常见问题
 
1.分配操作造成的速度减慢 光分配就耗费很长时间 最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块
2.释放操作造成的速度减慢 释放操作耗费较多周期,主要是启用了收集操作 收集期间,每个释放操作 查找 它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表 在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低
3.堆 竞争造成的速度减慢 当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争 竞争总是导致麻烦;这也是目前多处理 器系统遇到的最大问题
4.堆 破坏造成的速度减慢 造成堆破坏的原因是应用程序对堆块的不正确使用 通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题
5.频繁的分配和重分配造成的速度减慢 这是使用脚本语言时非常普遍的现象 如字符串被反复分配,随重分配增长和释放 不要这样做,如果可能,尽量分配大字符串和使用缓冲区 另一种方法就是尽量少用连接操作
 

5、程序代码区

存放函数体的二进制代码
 

#include<stdio.h>

int g1=0,g2=0, g3=0;

int main()

{

static int s1=0,s2=0, s3=0;

int v1=0,v2=0, v3=0;

//打印出各个变量的内存地址

printf("0x%08x\n",&v1);//打印各本地变量的内存地址

printf("0x%08x\n",&v2);

printf("0x%08x\n\n",&v3);

printf("0x%08x\n",&g1);//打印各全局变量的内存地址

printf("0x%08x\n",&g2);

printf("0x%08x\n\n",&g3);

printf("0x%08x\n",&s1);//打印各静态变量的内存地址

printf("0x%08x\n",&s2);

printf("0x%08x\n\n",&s3);

return0;

}

 
 
输出的结果就是变量的内存地址 。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。 这是因为本地变量和全局 / 静态变量是分配在不同类型的内存区域中的结果 全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中 程序通过堆栈的基地址和偏移量来访问本地变量
 
 
例子程序
  int a = 0; // 全局初始化区
  char *p1; // 全局未初始化区
  main()   
  {   
    int b; // 栈   
    chars[] = " abc "; // 栈   
    char *p1, *p2; // 栈   
    char*p3 = "123456";
    // 123456\0在常量区,p3在栈上。
    static int c =0; // 全局(静态)初始化区
    p1 =(char *) malloc (10);  
    p2 =(char *) malloc (20);
     strcpy (p1,"123456");  
  }   
  分配得来得10和20字节的区域就在堆区。   
  strcpy (p1,"123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
 
 
静态存储区和堆栈
我们通过代码段来看看对这样的三部分内存需要怎样的操作和不同,以及应该注意怎样的地方。
 

#include<stdio.h>

char *get_str()
{
  char*str="abcd";   //"abcd"在文字常量区
  return str;                //返回指向文字常量区的指针
}
void main()
{
  char *p=get_str();
  printf(p);
}
//执行正确,指向的是静态存储区常量
 
char *p = HelloWorld1
char a[] = HelloWorld2
p[2] = A ;
a[2] = A ;
char*p1 = HelloWorld1;
 
这个程序是有错误的,错误发生在 p[2] = A 这行代码处,为什么呢,是变量 p 和变量数组 a 都存在于栈区的(任何临时变量都是处于栈区的,包括在 main ()函数中定义的变量)。但是,数据 HelloWorld1 和数据 HelloWorld2 是存储于不同的区域的。
   因为数据“HelloWorld2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任何问题的 。因为指针变量 p 仅仅能够存储某个存储空间的地址,数据 HelloWorld1 为字符串常量,所以存储在静态存储区。虽然通过 p[2]可以访问到静态存储区中的第三个数据单元,即字符l所在的存储的单元。但是因为数据HelloWorld1 字符串常量,不可以改变,所以在程序运行时,会报告内存错误。并且,如果此时对 p p1 输出的时候会发现 p p1 里面保存的地址是完全相同的。换句话说,在数据区只保留一份相同的数据。
  char * str =NULL; // str 是在栈区中 
  str =(char *) malloc (MAX* sizeof (char));
  // str 申请的空间在堆中
  strcpy ( str,"Welcome toC++ world!");
  // 将静态存储区的字符串复制给 str ,不是指针指向
  str [2] = 'a';// 这样就可以修改 str 的内容了,此时
                   // 静态存储区的字符串已复制到堆中
  printf ("%s\ n",str );
 
1.申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间   
heap:
需要程序员自己申请,并指明大小,在 c malloc 函数
p1 =(char *) malloc (10);
C++ 中用 new 运算符
p2 =new char[20];//(char *) malloc (10);
但是注意 p1 p2 本身是在栈中的。
 
2.申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。  
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete 语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
 
3. 请大小的限制
栈:在 Windows , 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS 下,栈的大小是 2M (也有的说是 1M ,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示 overflow 。因此,能从栈获得的空间较小。   
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
 
4. 申请效率的比较
栈由系统自动分配,速度较快。但程序员是无法控制的。   
堆是由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片 , 不过用起来最方便 .   另外,在 WINDOWS 下,最好的方式是用 VirtualAlloc 分配内存,他不是在堆,也不是在栈 , 而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活
 
5.堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。  当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。   
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
 
void func(int param1,  int param2, int param3)
{
int var1=param1;
int var2=param2;
int var3=param3;
//打印出各个变量的内存地址
printf("0x%08x\n",&param1);
printf("0x%08x\n",&param2);
printf("0x%08x\n\n",&param3);
printf("0x%08x\n",&var1);
printf("0x%08x\n",&var2);
printf("0x%08x\n\n",&var3);
}
void main()
{
func(1,2,3);
}
 
 
 
6 、存取效率的比较
chars1[] = " aaaaaaaaaaaaaaa ";
 char *s2 = " bbbbbbbbbbbbbbbbb "; aaaaaaaaaaa 是在运行时刻赋值的;而 bbbbbbbbbbb 是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串 ( 例如堆 ) 快。比如:
存取效率的比较
对应的汇编代码   
10: a= c[1];
004010678A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A88 4D FC mov byte ptr [ebp-4], cl
11: a= p[1];
0040106D8B 55 EC mov edx,dword ptr [ebp-14h]
004010708A 42 01 mov al,byte ptr [edx+1]
0040107388 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器 cl 中,而第二种则要先把指针值读到 edx 中,在根据 edx 读取字符,显然慢了。
总之,对于 堆区、栈区和静态存储区它们之间最大的不同在于,栈的生命周期很短暂。但是堆区和静态存储区的生命周期相当于与程序的生命同时存在(如果您不在程序运行中间将堆内存delete的话),我们将这种变量或数据成为全局变量或数据。但是,对于堆区的内存空间使用更加灵活,因为它允许你在不需要它的时候,随时将它释放掉,而静态存储区将一直存在于程序的整个生命周期中。
 
 
 
 
 
 
 
 
 
 
 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值