最近看了些object-c的东西,对其使用引用计数实现内存自动管理比较感兴趣,但是觉得其保留retain/release允许手工处理内存又觉得是个失败.不由的思考起来,如果是我扩充c语言,该如何去做. c语言在移动开发中,拥有无以伦比的优势:能够实现精细度极高的程序实现,充分利用设备的性能.
注意,除了C语言,原则上与其他任何语言平台没有任何关联.
先从一个简单的函数说起:
void function(int a, struct object* b)
{
int i;
int *j;
struct object A;
struct object *B;
j = malloc(sizeof(int));
B = &A;
}
struct object 代表一个确定的结构体,这里没有给出定义.从第一行开始,我来仔细分析下这些变量所在的内存区域.
a与b是传入的变量,因此,a与b都存在于栈空间中,b是一个地址变量,它指向了一个struct object的对象或者一个NULL,如果它指向的struct object不为空,则该对象既有可能存在于栈中,也有可能存在于堆中,这取决于该对象申请空间的方式.
i是一个int类型的变量,该变量存储于栈中.
j是一个指向int类型的指针变量,j存储在栈中,其指向的变量未定.
A是一个struct object类型的变量,该变量存储在栈中. 其实就广义上来说,struct object与int没有任何区别,唯一的区别在于,int是内置类型,而struct object是开发者自己组织并向操作系统声明的类型.我在这里既写了int与struct,目的就是指出这个.
B是一个指针类型,B存储在栈中,而B指向的具体地址未定.
j = malloc(sizeof(int)); 为j在堆中申请一段内存,该内存长度为int的长度,此时,j的值为申请成功的内存的地址,*j代表了堆中内存本身.理论上来讲,如果在该句以前,添加*j = i会引发错误,因为j指向的地址不确定,如果修改了不确定的地址,会引发不可预料的结果;但是如果在该句之后执行*j=i;则完全没有问题,因为*j的内存地址是开发者自己申请来的. 在函数结束前,因为没有free(j); 因此,该函数存在内存泄露. 如果开发者在该句以后增加一句j=&i;那么,即使在函数末尾加上了free(j),也会引发一系列的错误,因为,j的地址已经改变,指向栈空间.
B = &A; 该行是将A的地址,赋值给B变量. 此时,*B与A等价,修改*B,就是修改A.
再写一个简单的例子.
void function1(int c, int* d)
{
c = 5;
*d = 6;
}
void main()
{
int a = 1;
int b = 2;
function1(a,&b);
printf("a=%d b=%d",a,b);
}
很明显,结果应该是a=1 b=6. 但是,why?很多人都会说,因为第二个参数传递了b的地址,其实传递地址,仅仅是将b所在栈中的地址值,拷贝给了变量d,与将a的值,拷贝给变量c,没有任何区别.但是*d提供了访问b的方式. 如果程序变成 function(a,&(&b)), 先不理会是否能够编译过去或者合法等,因为&b这个变量,没有任何空间来存储,所以,对&b再取&,应该非法.但是如果修改成 int *x; x=&b; function1(a,&x); 那应该没有任何逻辑上的问题,因为x存在于栈中,对其取地址完全合法.当然,在进行类型检查时会出错,因为function1的第二个参数要求的是int指针变量,而传入的是一个指针指针值.
通过上边的例子,可以说明这么几点:
1, 内存栈有专门的维护,如果是在栈中申请的变量,其代表的内存不需要释放,也不可能存在内存泄露问题.只会存在内存不够用的问题.
2,指针只是一种基本类型,它的使用,与其他任何基本类型或者自定义类型都没有任何关系.指针的指向,也与堆和栈没有任何关系.
3,函数入参,不管传递的是什么,都是以全拷贝的方式传入.只不过如果拷贝的是指针变量,可以间接实现内存的公用访问.
4,如果一个指针类型变量指向了某一块在堆中新申请的内存,我们一般不能再更改它的指向,因为如果更改了指针变量的值,申请的内存区域极可能存在泄漏.
5,如果函数能够准确的预知自己需要的空间,那么,完全可以将变量与空间分配到栈中,而不使用堆,也就是程序不会存在内存泄露的风险.虽然结果往往是工作中内存大小不可预知.
6,即使所有变量都分配在栈中,直接修改当前父栈的变量,也是完全可能的,只要能够知道父栈变量的地址.
但是悲剧的是,很多时候,程序需要多少内存,是随着运行情况的变化而变化的,所以总是需要动态的申请与释放内存.动态的申请了内存,忘记释放,或者大意将指向该内存的指针指向修改,或者指针经过多次返回,没人能够确定该内存区域变化,也没人敢去释放它,都会导致内存泄露. 另外,实际使用的内存已经释放,但是仍然使用指向的指针,也是经常使程序产生错误的一个问题.这也是C语言中内存 "谁申请,谁释放"的原则来由.
既然明白了内存泄露的缘由,那为C扩展自动内存管理,也就有了依据.
Apple 从XCode4 开始已经提供了ARC,所以,后边文章没有再写. 这部分就当理解ARC如何工作吧.