va_args()引起EXC_BAD_ACCESS错误

5

在使用va_args(iOS 7、Xcode 5.1.1、ARC开启)时,我遇到了EXC_BAD_ACCESS错误:

    // ...
    int val = sqlIntQuery(@"format_string", @"arg1"); // <-- does not work
    int val = sqlIntQuery(@"format_string", @"arg1", nil); // <-- this works
    // ...

- (int)sqlIntQuery:(NSString *)format, ...
{
    va_list args;
    va_start(args,format);
    __unsafe_unretained id eachObject;
    NSMutableArray *arguments = [NSMutableArray array];
    while ( (eachObject = va_arg(args, id)) != nil ) { // <-- crash on 2nd loop
        [arguments addObject:eachObject];
    }
    va_end(args);

    // ... process 'arguments'

    return 5; // return a computed intValue
}

如果我在循环的结尾加上“break;”(因为我只有一个参数),或者将“nil”作为最后一个参数添加,就不会崩溃,但我认为我不应该添加“nil”。我怀疑这是ARC问题,但我正在使用__unsafe_unretained,就像其他人在SO上建议的那样。(有没有办法将“nil”推入args中?)
是什么导致第二次循环失败?
编辑于8月6日:我的解决方案:
当maddy提到“格式说明符的数量”时,他所接受的解决方案将我引向了正确的方向。我的格式参数对于每个参数都有一个“?”占位符,所以我只需计算它们的数量。因此,记录如下:
- (int)sqlIntQuery:(NSString *)format, ...
{
    int numberOfArgs = [format componentsSeparatedByString:@"?"].count - 1; // <<-- this solved my problem

    va_list args;
    va_start(args,format);
    NSMutableArray *arguments = [NSMutableArray array];
    while ( numberOfArgs-- ) {
        id eachObject = va_arg(args, id);
        [arguments addObject:eachObject];
    }
    va_end(args);

    FMResultSet *rs = [db executeQuery:format withArgumentsInArray:arguments];
    [rs next];
    int ret = [rs intForColumnIndex:0];
    [rs close];

    return ret;
}

这是一个双层包装。我的程序是对FMDB进行的封装,而FMDB本身则是对SQLite进行的封装。


3
为什么你认为不应该使用“nil”作为终止符/哨兵?我认为va_arg不支持这种类型的循环。 - bperson
请记住,您的解决方案虽然不错,但很脆弱。如果传递了额外的参数,它们将被静默地忽略。如果传递的参数太少,则应用程序可能会崩溃或更糟糕的是,它可能会运行但使用错误的数据。请小心。 - rmaddy
1个回答

7
你需要使用nil或其他方法来确定要获取的参数数量。 va_list没有魔法方法来知道何时停止。
stringWithFormat:这样的东西不需要nil,因为它根据格式说明符的数量确定参数的数量(这就是为什么它们需要匹配,否则你的代码会崩溃)。请注意,像NSDictionary dictionaryWithObjectsAndKeys:这样的方法需要nil终止符,或者像UIAlertView initWithTitle...这样的方法需要otherButtonTitles参数的nil终止符。
你可以利用以下NSString方法:
- (int)sqlIntQuery:(NSString *)format, ... {
    va_list args;
    va_start(args, format);
    NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
    // do whatever
    va_end(args);

    return 5;
}

当然,假设您希望从format和方法的变量参数构建字符串来解决此问题。
如果您确实需要填充数组,则在调用sqlIntQuery方法时需要传递一个nil终止符。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接