以下均来自网络高手所列举出来的问题,绝非自己写作。只是自己也有不太深入了解的地方。作为笔录,以待深入研究学习!
*******************************************************************************************************************************************************
下面的例子说明指针和数组的区别
- char* p = “Hello World1”;
- char a[] = “Hello World2”;
- p[2] = ‘A’;
- a[2] = ‘A’;
- 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.频繁的分配和重分配造成的速度减慢
。
这是使用脚本语言时非常普遍的现象
。
如字符串被反复分配,随重分配增长和释放
。
不要这样做,如果可能,尽量分配大字符串和使用缓冲区
。
另一种方法就是尽量少用连接操作
#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",¶m1);
printf("0x%08x\n",¶m2);
printf("0x%08x\n\n",¶m3);
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的话),我们将这种变量或数据成为全局变量或数据。但是,对于堆区的内存空间使用更加灵活,因为它允许你在不需要它的时候,随时将它释放掉,而静态存储区将一直存在于程序的整个生命周期中。