内存
今天我们来对copy,retain,assign来一个比较深刻的认识,弄明白为什么要这么做!数据乃以生存的基础那就是内存。
内存区域
有人认为三种,也有人认为四种,还有人认为是五种,我相信他们都有道理,没必要深究。这里以五种为例,其实我们只需要知道前面三个就够了。
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放 , 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态 变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(BSS)。 - 程序结束后由系统释放4、文字常量区 — 常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区— 存放函数体的二进制代码。
- stack
- 一组数据的存放方式,特点为LIFO,即后进先出(Last in, first out)。
- call stack,也就是调用栈,表示函数像堆积木一样存放,以实现层层调用
class Student{
int age;
String name;
public Student(int Age, String Name)
{
this.age = Age;
setName(Name);
}
public void setName(String Name)
{
this.name = Name;
}
}
public class Main{
public static void main(String[] args) {
Student s;
s = new Student(23,"Jonh");
}
}
首先调用main方法,里面需要生成一个Student的实例,于是又调用Student构造函数。在构造函数中,又调用到setName方法。这三次调用像积木一样堆起来,程序运行的时候,总是先完成最上层的调用,然后将它的值返回到下一层调用,直至完成整个调用栈,返回最后的结果。Stack frame(堆栈帧)是一个为函数保留的区域,用来存储关于参数、局部变量和返回地址的信息。堆栈帧通常是在新的函数调用的时候创建,并在函数返回的时候销毁。
3. 存放数据的一种内存区域,程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做stack(栈),另一种叫做heap(堆)。它们的主要区别是:stack是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小;heap是没有结构的,数据可以任意存放。因此,stack的寻址速度要快于heap。其他的区别还有,一般来说,每个线程分配一个stack,每个进程分配一个heap,也就是说,stack是线程独占的,heap是线程共用的。此外,stack创建的时候,大小是确定的,数据超过这个大小,就发生stack overflow错误,而heap的大小是不确定的,需要的话可以不断增加。
public void Method1()
{
int i=4;
int y=2;
class1 cls1 = new class1();
}
上面代码的Method1方法,共包含了三个变量:i, y 和 cls1。其中,i和y的值是整数,内存占用空间是确定的,而且是局部变量,只用在Method1区块之内,不会用于区块之外。cls1也是局部变量,但是类型为指针变量,指向一个对象的实例。指针变量占用的大小是确定的,但是对象实例以目前的信息无法确知所占用的内存空间大小。
![这里写图片描述](http://image.beekka.com/blog/201311/bg2013112905.jpg)
接下来的问题是,当Method1方法运行结束,会发生什么事?
回答是整个stack被清空,i、y和cls1这三个变量消失,因为它们是局部变量,区块一旦运行结束,就没必要再存在了。而heap之中的那个对象实例继续存在,直到系统的垃圾清理机制(garbage collector)将这块内存回收。因此,一般来说,内存泄漏都发生在heap,即某些内存空间不再被使用了,却因为种种原因,没有被系统回收。
下面看看变量具体分布实例:
using namespace std;
static int a=1;//全局初始化区
int b=2;//全局初始化区
char *p;//全局未初始化区
char *p2;//全局未初始化区
int *p3;//全局未初始化区
int *p4;//全局未初始化区
char *p5={"555555555"};//全局初始化区
main(){
static int c=3;
int d=4;//内存栈
int e=7;//内存栈
char *p6={"555555555"};
p=(char*)malloc(sizeof(char)*10);//内存堆
p2=(char*)malloc(sizeof(char)*10);//内存堆
p3=(int*)malloc(sizeof(int));//内存堆
p4=(int*)malloc(sizeof(int)*10);//内存堆
for(int i=0;i<=9;i++)p4[i]=0x1;
*p3=0x123;
strcpy(p,"123456789");//文字常量区
strcpy(p2,"987654321");
strcpy(p2,"123456789");
}
1.可以看到全局初始化区与全局(静态)初始化区是在同一段内存 连续分配的,按内存地址增长方向分配
2.全局初始化区与全局未初始化区不在同一段内存区,全局初始化区好像按内存地址增长方向分配,全局未初始化区好像按内存增长的方向貌似没有规律!!
3.可以看到栈空间也是同一段内存 连续分配的,按内存地址减小方向分配
4.可以看到堆空间也是同一段内存 连续分配的,按内存地址增大方向分配
5.文字常量区在自己特有的内存段内,且有机制控制字符常量不被修改(当字符串相同的时候,系统有时还会将两个指针指向同一处)
注意 :在文字常量区的字符串不可以被修改,而在内存堆空间的字符串可以被修改!!
下面介绍函数参数压栈顺序:
void func(int k,char * sz,int b)
{
return;
}
void main(void)
{
int i = 0x22222222;
char szTest[] = "abcd";
int fb = 0x11111111;
func(i,szTest,fb);
}
第一,参数压栈的顺序是从右到左,对应到上面的代码也就是先压b,然后是sz,最后是k。 第二,栈空间是从高地址向低地址发展的。
所以,我们可以估计出他们在内存中的分布,从高地址到低地址的顺序是:b, sz,k。 同时由于三个参数都占4个字节大小,所以两个参数的首地址之间相隔四个字节。
运行上述程序,动态观察三个变量的内存分布如下:
变量b在内存中的地址为:&b = 0x0025fa90
变量sz在内存中的地址为:&sz = 0x0025fa8c
变量k在内存中的地址为:&k = 0x0025fa88
上述地址印证了我们的说法。
另外,栈中的sz存储的是什么呢? 对了,就是主函数中的char 数组的首地址。观察内存,我们看到szTest的首地址为:0x0025fb40, 所以,sz的值就应该是0x0025fb40
,通过查看内存变量,发现事实就是如此。
待续
参考:http://www.cnblogs.com/huhuuu/p/3440187.html
http://www.ruanyifeng.com/blog/2013/11/stack.html
http://blog.csdn.net/wealoong/article/details/8686353