va_args() causing EXC_BAD_ACCESS
Asked Answered
M

1

5

I'm getting a EXC_BAD_ACCESS when using va_args (iOS 7, Xcode 5.1.1, ARC on):

    // ...
    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
}

If I put a "break;" at the end of the loop (because I only have one argument), or add "nil" as a last argument, there's no crash, but I don't think I should have to add the "nil". I suspect an ARC issue, but I'm using __unsafe_unretained, as suggested elsewhere on SO. (Is there a way I can push "nil" into to args?)

What's causing the failure on the second time through the loop?


EDIT Aug 6: My Solution:

The accepted solution by maddy pushed me in the right direction when he mentioned "the number of format specifiers." My format argument has a '?' placeholder for each argument, so I just count those. So, for the record:

- (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;
}

It's a double-wrapper. My routine is a wrapper around FMDB, which is itself a wrapper for SQLite.

Military answered 4/8, 2014 at 20:17 Comment(2)
Why do you think you should not use "nil" as a terminator / sentinel ? I don't think that kind of looping is supported by va_arg.Dogberry
Keep in mind that your solution, while good, is fragile. If extra arguments are passed they will be ignored silently. If too few arguments are passed then the app could crash or worse, it could run but with bad data. Be careful.Chlamydate
C
7

You need the nil or some other way to know how many arguments to grab. va_list has no magic way to know when to stop.

Things like stringWithFormat: don't need a nil because it determines the number of arguments based on the number of format specifiers (which is why they need to match or your code goes boom). Note how methods like NSDictionary dictionaryWithObjectsAndKeys: requires a nil terminator or UIAlertView initWithTitle... needs the nil terminator for the otherButtonTitles parameter.

What you could do is make use of the following NSString method:

- (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;
}

Of course this solutions assumes you wish to build a string from format and the variable arguments to your method.

If you really do need to populate an array then you will need to pass a nil terminator when you call your sqlIntQuery method.

Chlamydate answered 4/8, 2014 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.