警告块捕获一个自动释放的out参数



在我使用的第三方库中,我得到警告

"块捕获自动释放的out-parameter">

是什么问题,我怎么解决它?

- (BOOL)register:(NSString *)param error:(NSError **)errPtr
{   
__block BOOL result = YES;
__block NSError *err = nil;

dispatch_block_t block = ^{ @autoreleasepool {
NSMutableArray *elements = [NSMutableArray array];
/**** Block captures an autoreleasing out-parameter,
which may result in use-after-free bugs ****/
/* on errPtr */
[self registerWithElements:elements error:errPtr];

}};

if (errPtr)
*errPtr = err;

return result;
}

当你有一个间接的非常量参数(T **param) Clang与ARC自动限定这样的参数与__autoreleasing(T *__autoreleasing*)。这是因为Clang合理地假设,调用方并不总是需要释放这样的对象,所以它要求函数只分配自动释放对象。因此:

- (void)myMethod:(NSObject **)param {
*param = [NSObject new];
}

在ARC下变为:

- (void)myMethod:(NSObject *__autoreleasing*)param {
*param = [[NSObject new] autorelease];
}

这反过来又对这种方法的参数提出了特殊要求,因此在通常情况下,您实际上只是将一些(强保留)对象传递给函数:

NSObject *obj;
[self myMethod:&obj];

ARC实际上创建了一个临时的自动释放参数:

NSObject *__strong obj = nil;
NSObject *__autoreleasing tmp = obj;
[self myMethod:&tmp];
obj = [tmp retain];

是什么问题…

如果不是(间接)传递强保留指针,而是传递自己的间接指针,ARC不会在两者之间创建任何临时指针:

NSObject *__autoreleasing obj;
NSObject *__autoreleasing *objPtr = &obj;
[self myMethod:objPtr];

表示对象"返回";myMethod:不再被保留,因此当当前的自动释放池被排干时将被破坏。如果传递具有相同语义的参数,则同样成立:

- (void)anotherMethod:(NSObject **)param {
[self myMethod:param];
}
因此,如果出于某种原因,您决定用自动释放块包装myMethod:的调用,那么这里的代码最终会得到一个僵尸对象:
- (void)anotherMethod:(NSObject **)param {
@autoreleasepool {
[self myMethod:param]; // object was created and assigned to a autoreleasing pointer
} // ref. count of the object reached zero. `*param` refers to a released object
}

如果用块封装调用,也可能发生同样的

- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
// "Block captures an autoreleasing out-parameter, which may result in use-after-free bugs" warning appears
[self myMethod:param];
};
block();
}

对于这个特定的实现,不会发生任何问题,您可以通过显式地提供间接指针__autoreleasing限定符(通过此限定符,您可以告知Clang您很清楚可能的后果)来沉默错误:

- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
block();
}

但是现在你必须非常小心,因为block是第一方对象,它可以在任何地方被保留和调用,并且有无数的场景会产生额外的自动释放池。例如,下面的代码将产生相同的zombie-object错误:

- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
... some code here ...
@autoreleasepool {
block();
}
}

如果自动释放池正好在块体中,则相同:

- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
@autoreleasepool {
[self myMethod:param];
}
};
block();
}

话虽如此,Clangs不会警告错误(在您的情况下,这实际上很明显,因为您用@autoreleasepool块包装了块体),它只是希望您仔细检查您是否意识到可能存在的问题(正如您所看到的,仍然有可能实现这样的事情,但如果出现所有错误,您将很难跟踪它们)。


我该如何修复它?

这取决于你对"固定"的定义。您可以从块体中删除自动释放池块,并显式地限定__autoreleasing参数(只要它只是在方法中的同一线程中调用):

- (BOOL)register:(NSString *)param error:(NSError *__autoreleasing*)errPtr {   
....

dispatch_block_t block = ^{
....
[self registerWithElements:elements error:errPtr];

};
block();
....
}

或者你可以介绍另一个"本地的";变量来捕获并在块中传递它:

- (BOOL)register:(NSString *)param error:(NSError **)errPtr {
....
__block NSError *err;    
dispatch_block_t block = ^{
@autoreleasepool {
....
[self registerWithElements:elements error:&err];
}
};
block();
*errPtr = err;
....
}

这再次暗示该块在方法中被同步调用,但不一定在相同的自动释放池块中。如果您希望存储块以供以后使用,或者异步调用它,那么您将需要另一个具有延长生存期的NSError变量来在块中捕获。

最新更新