Cocoa / ARC - 为什么将结构作为参数传递导致自动释放池上的错误访问?

时间:2012-01-11 05:02:07

标签: cocoa parameters struct exc-bad-access automatic-ref-counting

在我发现它在模拟器上工作正常后,我刚刚完成调试我的项目的地狱,但是在使用ARC的@autoreleasepool上在具有EXC BAD ACCESS错误的设备上进行测试时会崩溃。

我最终将问题缩小到我为自定义类创建的自定义init方法的位置,该方法接受了两个结构作为参数。结构基于相同的定义,仅包含3个GLfloats来表示用于定位和旋转的x,y和z数据。

当我修改自定义init方法而不是接受6个GLfloats而不是每个包含3个GLfloats的两个结构时,然后让init方法将这些GLfloats分配给类的两个适当的实例变量结构,而不是分配先前传递的结构直接到实例变量结构,它一切正常,没有错误。

稍微澄清一下:

我有一个像这样定义的结构:

struct xyz{
    GLfloat x;
    GLfloat y;
    GLfloat z;
};

然后我有两个自定义类的ivars(让我们称之为foo),称为位置和旋转,都基于这个结构。

然后我将自定义的init方法简化为:

-(id) initWithPosition: (struct xyz) setPosition rotation: (struct xyz) setRotation
{
    self = [super init];
    if(self) {
        position = setPosition;
        rotation = setRotation;
    }
    return self;
}

我打电话使用类似的东西:

struct xyz position;
struct xyz rotation;

// Fill position / rotation with data here....

foo *bar = [[foo alloc] initWithPosition: position rotation: rotation];

导致了EXC BAD ACCESS。所以我将init方法更改为:

-(id) initWithPositionX: (GLfloat) xp Y: (GLfloat) yp Z: (GLfloat) zp RotationX: (GLfloat) xr Y: (GLfloat) yr Z: (GLfloat) zr
{
    self = [super init];
    if(self) {
        position.x = xp;
        position.y = yp;
        position.z = zp;

        rotation.x = xr;
        rotation.y = yr;
        rotation.z = zr;

    }
    return self;
}

而且......一切都很好。

我的意思是,我很高兴我"修复"它,但我不知道为什么我修复它。我读过ARC有使用对象结构的问题,但我的结构只包含简单的GLfloats,它们不是对象......对吗?我宁愿知道为什么这样做才能继续前进,希望能帮助其他一些人遇到同样的问题。

谢谢, - Adam Eisfeld

编辑:添加来源

尝试我似乎无法在测试项目中复制该问题,因此我的代码肯定有问题,正如其他人所建议的那样,我根本没有解决问题,只是将其掩盖到以后这是我害怕的。

所以如果有人可以看看这段代码来指出这里可能出现的问题(如果问题确实存在于init方法中,可能不是这样),我非常感激。它相当大,所以我理解,如果没有人感兴趣,但我评论它和Ill解释在代码片段后进一步详细说明:

-(id) initWithName: (NSString*) setName fromFile: (NSString*) file
{
    self = [super init];
    if(self) {

        //Initialize ivars
        name = setName;
        joints = [[NSMutableArray alloc] init];
        animations = [[NSMutableArray alloc] init];
        animationCount = 0;
        frameCount = 0;

        //Init file objects for reading
        NSError *fileError;
        NSStringEncoding *encoding;

        //Load the specified file's contents into an NSString
        NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:encoding error:&fileError];

        //Create a new NGLMaterial to apply to all of the loaded models from the file
        NGLMaterial *material = [[NGLMaterial alloc] init];
        material = [NGLMaterial material];
        material.diffuseMap = [NGLTexture texture2DWithFile:@"resources/models/diffuse.bmp"];

        //Check for nil file data
        if(fileData == nil)
        {
            NSLog(@"Error reading mesh file");
        }
        else
        {
            //Separate the NSString of the file's contents into an array, one line per indice
            NSMutableArray *fileLines = [[NSMutableArray alloc] initWithArray:[fileData componentsSeparatedByString:@"\n"] copyItems: YES];

            //Create a pseudo counter variable
            int i = -1;

            //Allocate a nul NSString for looping
            NSString *line = [[NSString alloc] initWithFormat:@""];

            //Loop through each of the lines in the fileLines array, parsing the line's data
            for (line in fileLines) {

                //Increase the pseudo counter variable to determine what line we're currently on
                i++;

                if (i == 0) {
                    //The first line of the file refers to the number of animations for the player
                    animationCount = [line intValue];
                }else
                {

                    if (i == [fileLines count]-2) {
                        //The last line of the file refers to the frame count for the player
                        frameCount = [line intValue];

                    }else
                    {
                        //The lines inbetween the first and last contain the names of the .obj files to load

                        //Obtain the current .obj path by combining the name of the model with it's path
                        NSString *objPath = [[NSString alloc] initWithFormat:@"resources/models/%@.obj", line];

                        //Instantiate a new NGLMesh with the objPath NSString
                        NGLMesh *newMesh = [[NGLMesh alloc] initWithOBJFile:objPath];

                        //Apply various settings to the mesh such as material
                        newMesh.material = material;
                        newMesh.rotationOrder = NGLRotationOrderZYX;

                        //Compile the changes to the mesh
                        [newMesh compileCoreMesh];

                        //Add the mesh to this player's joints array
                        [joints addObject:newMesh];

                        //Read the animation data for this joint from it's associated file
                        NSLog(@"Reading animation data for: %@", line);

                        //The associated animation file for this model is found at (model's name).anim
                        NSString *animData =  [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:line ofType:@"anim"] usedEncoding:encoding error:&fileError];

                        //Check for nil animation data
                        if(animData == nil)
                        {
                            NSLog(@"Error reading animation file");
                        }
                        else
                        {

                            //Construct temporary position and rotation structs to store the read xyz data from each frame of animation
                            struct xyz position;
                            struct xyz rotation;

                            //Create a new scanner to scan the current animation file for it's xyz position / rotation data per frame
                            NSScanner *scanner = [[NSScanner alloc] initWithString:animData];
                            while([scanner isAtEnd] == NO)
                            {

                                //Extract position data
                                [scanner scanFloat:&position.x];
                                [scanner scanFloat:&position.y];
                                [scanner scanFloat:&position.z];

                                //Extract rotation data
                                [scanner scanFloat:&rotation.x];
                                [scanner scanFloat:&rotation.y];
                                [scanner scanFloat:&rotation.z];



                                //OLD CODE NOT WORKING:
                                //AEFrame *frame = [[AEFrame alloc] initWithPosition: position rotation: rotation];

                                //Initialize new frame instance using new working init method
                                AEFrame *frame = [[AEFrame alloc] initWithPositionX:position.x Y:position.y Z:position.z RotationX:rotation.x Y:rotation.y Z:rotation.z];

                                //Add the created frame instace to the player's animations array
                                [animations addObject:frame];

                            }  
                        }   
                    }
                }
            }
        }
    }
    return self;
}

好吧,基本上我正在编写代码来处理我的引擎中给定玩家的3D关节的动画。我在MEL中为Maya编写了一个脚本,允许您在视口中选择一系列动画模型(在这种情况下,机器人角色IE的关节,上下臂,头部,躯干等)然后运行脚本将遍历每个选定的模型并导出.anim文件。这个.anim文件包含文件在其动画的每一帧引用的关节的xyz位置和旋转,因此它的结构如下:

Frame 1 X Position
Frame 1 Y Position
Frame 1 Z Position
Frame 1 X Rotation
Frame 1 Y Rotation
Frame 1 Z Rotation
Frame 2 X Position
Frame 2 Y Position
etc...

然而,这个动画文件完全由浮点引用xyz位置和旋转组成,上面没有实际文本标记每一行,仅供参考。

当为动画角色的每个选定关节完成.anim文件的导出时,该脚本会导出一个扩展名为.dat的最终文件。此文件包含导出的动画量(此值在脚本中设置,例如,您可能将3个动画导出到.anim文件,如“运行”,“漫游”和“空闲”),然后包含名称列表。这些名称既指向要加载到3D引擎中的.obj文件的名称,也指向要加载的.obj文件的相应.anim文件。

有了解释,Ill描述了我在项目中如何处理这个问题:

  1. 用户实例化一个新的" AEPlayer"使用上述方法的类-initWithName:File:

  2. 此方法首先打开从maya导出的.dat文件,以查找要为播放器关节加载的文件的名称。

  3. 对于.dat文件中找到的每个联合名称,系统继续使用联合的名称实例化一个新的NGLMesh实例,其中包含" .obj"在它的末尾,然后打开当前联合的.anim文件。系统还会将实例化的NGLMesh添加到玩家的关节阵列中。

  4. 对于每个联合的.anim文件,它使用NSScanner读取文件。它一次扫描6行(xyz位置和每帧的xyz旋转)。在一帧的数据中扫描之后,它实例化一个叫做AEFrame的类,并将帧的位置数据设置为加载的位置数据,并将旋转数据设置为加载的旋转数据。这是我在将xyz位置结构和xyz旋转结构传递到它的init参数时遇到问题的类,但是" fixed"通过传入6个GLfloats,如上面的代码所示。

  5. 当帧已实例化时,此帧将添加到当前播放器的动画数组(NSMutableArray)中。这个动画数组最终变得相当大,因为它存储了模型中每个关节的所有动画数据。我正在测试的模型有42个关节,每个关节有12帧动画,每帧有6个GLfloats。

  6. 当系统完成将.dat文件中找到的所有.obj文件及其关联的.obj和.anim文件加载到内存中时,它就完成了。

  7. 在程序的主运行循环中,我只是循环遍历所有创建的AEPlayers,并且对于每个AEPLayer,我遍历播放器的关节数组,设置存储的NGLMesh的位置和旋转在当前玩家的联合中,在当前联合的玩家动画阵列中找到任何位置/旋转。

  8. 我意识到要让所有人阅读所有这些内容是一个很长的镜头,但我想我会把它扔出去。另外,我知道我在上面的代码片段中有一些我需要摆脱的死分配,但他们并没有对这个问题做出贡献(我在问题出现之后添加了它们,看看发生了什么事情在内存方面使用了仪器)。

    再次感谢您的帮助, - Adam Eisfeld

1 个答案:

答案 0 :(得分:3)

其他一些代码可能会破坏堆栈,或者做一些未定义的其他代码。如果你正在做这样的事情(这可能是非常棘手的调试),所有的赌注都是关闭的,简单的改变可能掩盖或揭露真正的错误。

当你在设备和模拟器之间进行切换时,你也在构建ARM而不是i386,并且正在构建优化而不是调试。这些都是巨大的变化,可能会改变未定义行为的结果。例如,在数组末尾写一个字节可能不会对调试版本造成任何损害,但会在发布版本中造成灾难性故障。将结构更改为浮点数可能会改变参数传递到方法的方式,这可能会掩盖或取消屏蔽错误。

您应该尝试将问题与小型示例项目隔离开来。例如,你能用一个类重现问题,只用一个init方法吗?

如果您还不熟悉此主题,那么来自LLVM博客的这组文章可能会有所帮助:

<强>更新

看起来您的问题可能就在这里:

NSStringEncoding *encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:encoding error:&fileError];

-[NSString initWithContentsOfFile:usedEncoding:error]的usedEncoding参数是一个out参数,这意味着您提供存储并传递指向该存储的指针,该函数将向其写入一些输出。你已经提供了一个指针,但它没有指向任何有效的东西,当 - [NSString initWithContentsOfFile:usedEncoding:error]写入usedEncoding指针时,它将覆盖一些东西。

您应该将该代码更改为:

NSStringEncoding encoding;
//Load the specified file's contents into an NSString
NSString *fileData = [[NSString alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:file ofType:@"dat"] usedEncoding:&encoding error:&fileError];

现在,您已经提供了指向有效字符串编码变量的指针。