《原创作品,转载请注明出处》
花费三个小时,终于解决了一个特别隐蔽的内存问题。特此分享。希望各位不要在犯这类毛病~~真的会死人。
高手全当娱乐新闻,只望和我一样水平有限的同仁不要犯相同的错误。
问题代码及讲解如下。
原始问题代码(经过简化处理):
环境描述:mac, xcode4.2,iOS5(iphone4s),cocos2d工程,objective-c语言
可跳过代码往后看,后面描述很简单==========================================================
//通知执行指定的动画,action-动画类型
- (void)runSpecialAction:(HeroAction)action
{
//获得动画
CCAnimation *animation = [allSpecialAction objectForKey:[NSNumber numberWithInt:action]];
//创建动画
CCAnimate* animate = [CCAnimate actionWithAnimation:animation restoreOriginalFrame:NO];
//执行动画序列
[self runAction:[CCSequence actions:animate,
[CCCallFuncND actionWithTarget:self
selector:@selector(runSpecialActionDone:Data:)
data:(void *)&action], //action作为参数传递
nil]];
CCLOG(@"值:%x 地址:%08x", action, &action);
//输出 :值:4 地址:bfffdbe0
}
//指定的动画播放结束后执行此函数。data-数据
- (void)runSpecialActionDone:(CCNode *)pTarget Data:(void *)data
{
CCLOG(@"值:%x 地址%08x", *((int *)data), data);
//输出内容:值:98da0511 地址:bfffdbe0
...
}
两次地址相同,证明参数传递正常,既:函数1的action地址顺利传递到了函数2的data中。但对应的值却发生了变化,cocos2dBUG?NO。完全是程序逻辑问题。并且主要出在[CCSequence actions:(CCFiniteTimeAction *), ..., nil]上,其原因是CCSequence actions不会阻塞,甚至在函数1返回之后,函数2仍然没有得到执行。此时函数1被出栈,导致函数1栈上的action内存被重用。就产生了内存被莫名奇妙修改了的问题。好了,如果你已经了解,下面内容就不需要浪费时间看了。
仔细分析一下,问题变的非常简单。
=============================上面代码看不懂直接跳转到这里=============================
简化一下程序(c格式):
//产生问题的函数定义。参数:一个函数指针,以及一个void*作为参数(最终传递给pfun2指向的函数)。
//函数actions在另一个线程中执行,并且不会阻塞,甚至会在fun2调用开始之前就返回。
actions((void)(pfun2 *)(void *), void *a)
{
将pfun2(a)添加到一个委托程序,等待指定的事件发生后在执行;
返回;
}
(void)fun2(void *b)
{
//问题所在:b和fun1函数中的a具有相同地址,没有在任何地方修改,但是值却不同。
*(int *)b; //使用b。(将b转换成整型指针,间接访问,以获得其值)
}
(void)fun1(int a)
{
(void)(pfun2 *)(void *); //定义函数指针
pfun2 = fun2;
actions(pfun2, &a); //将函数指针传递给actions,并将a的地址作为参数
}
关于fun2中参数b的值和fun1中参数a的值不同,解释如下:
fun1调用actions,当actions函数返回后,fun1函数随即返回。这时,fun2函数还在等待执行。
注意fun2的参数void *b,b指向fun1函数的参数a(b存储着fun1函数的 参数a的地址)。
那么,fun2中*b(间接访问b),就应该是a的内容。
事实上我们的确在fun2中访问到了a的内容,只是这个内容已经被覆盖。
在我们访问*b时,fun1函数已经返回,而fun1的参数a只是fun1栈上的一个地址空间,
随着fun1返回,其栈空间已被释放(出栈),当然a也就不存在了。
所以我们通过fun2访问a时,得到了一个不确定的内容。
在系统内存利用率低的情况下,这种问题很难发现。但是在移动设备上内存紧缺,这种问题就容易暴露出来了。
堆栈调用如下:
fun1(int a)
{
通知actions调用fun2(a);
actions
{
将fun2代理给其他程序,并迅速返回到fun1;
}
actions出栈;
}
fun1出栈,同时参数int a被释放;
fun2(a)被调用:a已经被释放。
=完=