Variadic Function缓存最后一次调用的参数列表

时间:2017-10-17 12:42:52

标签: objective-c c variadic-functions

我使用以下模式为iOS库编写了一个带有c函数的Helper类。 有2个包装(可变参数)函数,它们最终调用相同的函数,参数略有不同。想法是"默认"正在设置的属性。

__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...);
__attribute__((overloadable)) void func1(int param1, NSString* _Nonnull format, ...);

然后两者都会调用以下函数:

void prefixAndArguments(int param1, NSString* _Nonnull format, va_list arguments);

实施如下:

__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...)
{
    va_list argList;
    va_start(argList, format);
    prefixAndArguments(0, format, argList);
    va_end(argList);
}

__attribute__((overloadable)) void func1(int param1, NSString* _Nonnull format, ...)
{
    va_list argList;
    va_start(argList, format);
    prefixAndArguments(param1, format, argList);
    va_end(argList);
}


void prefixAndArguments(NMXLogLevelType logLevel, NSString* _Nullable logPrefix, __strong NSString* _Nonnull format, va_list arguments)
{
    // Evaluate input parameters
    if (format != nil && [format isKindOfClass:[NSString class]])
    {
        // Get a reference to the arguments that follow the format parameter
        va_list argList;
        va_copy(argList, arguments);

        int argCount = 0;
        NSLog(@"%d",argCount);
        while (va_arg(argList, NSObject *))
        {
            argCount += 1;
        }
        NSLog(@"%d",argCount);
        va_end(argList);

        NSMutableString *s;
        if (numSpecifiers > argCount)
        {
            // Perform format string argument substitution, reinstate %% escapes, then print
            NSString *debugOutput = [[NSString alloc] initWithFormat:@"Error occured when logging: amount of arguments does not for to the defined format. Callstack:\n%@\n", [NSThread callStackSymbols]];
            printf("%s\n", [debugOutput UTF8String]);
            s = [[NSMutableString alloc] initWithString:format];
        }
        else
        {
            // Perform format string argument substitution, reinstate %% escapes, then print
            va_copy(argList, arguments);

            // This is were the EXC_BAD_ACCESS will occur!
            // Error: Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
            s = [[NSMutableString alloc] initWithFormat:format arguments:argList];
            [s replaceOccurrencesOfString:@"%%"
                               withString:@"%%%%"
                                  options:0
                                    range:NSMakeRange(0, [s length])];
            NSLog(@"%@",s);
            va_end(argList);
        }
    ...
}

我的单元测试功能如下所示(顺序很重要)。

// .. some previous cases, I commented out
XCTAssertNoThrow(NMXLog(@"Simple string output"));
XCTAssertNoThrow(NMXLog(@"2 Placeholders. 0 Vars %@ --- %@"));

当我想使用参数和格式(使格式强大无法解决问题,并且似乎不是问题的一部分,请参见下文)时发生崩溃:

s = [[NSMutableString alloc] initWithFormat:format arguments:argList];

这是日志:

xctest[28082:1424378] 0
xctest[28082:1424378] --> 1
xctest[28082:1424378] Simple string output
xctest[28082:1424378] 0
xctest[28082:1424378] --> 4

当然,我们之前没有看到所需的字符串"2 Placeholders. 0 Vars %@ --- %@"

所以,现在的问题是:为什么参数的数量现在是4而不是0?由于在第二次调用中没有传递,是否会在立即再次调用函数时收集参数?

所以,我开始再次打电话给#34;"尽管正在调用va_end,但要确保清除参数列表:

__attribute__((overloadable)) void func1(NSString* _Nonnull format, ...)
{
    va_list argList;
    va_start(argList, format);
    prefixAndArguments(none, nil, format, argList);
    va_end(argList);
    NSString *obj = nil;
    prefixAndArguments(none, nil, obj, nil);
}

这现在像魅力一样工作(正在清除参数列表并收到所需的输出):

xctest[28411:1453508] 0
xctest[28411:1453508] --> 1
xctest[28411:1453508] Simple string output
xctest[28411:1453508] 0
xctest[28411:1453508] --> 1
Error occured when logging: amount of arguments does not for to the defined format. Callstack: ....
xctest[28411:1453508] 2 Placeholders. 0 Vars %@ --- %@

最后我的问题是:

这种行为的原因是什么,我该如何避免?有没有更好的方法来解决这个问题,而不是愚蠢的"使用" no"第二次调用该函数清除它们的论据? 附:我尝试不使用宏,因为我认为它们比c函数更容易出错。请参阅此主题:Macro vs Function in C

1 个答案:

答案 0 :(得分:2)

你似乎对可变参数函数有一些误解,例如这种计算变量参数的方法:

        while (va_arg(argList, NSObject *))
        {
            argCount += 1;
        }

该代码假定变量参数至少有一个成员,所有成员都是NSObject *类型,并且该列表将由该类型的空指针终止。这些都不受系统保证,如果不满足这些假设,那么一个或多个va_arg()调用的行为将是未定义的。

在实践中,您可能可以使用其他类型指针的实际参数(虽然在形式上,在这种情况下仍然未定义行为)。但是,如果参数可能具有非指针类型,那么计算它们的方法就完全被打破了。更重要的是,您的测试用例似乎假设系统将提供一个尾随NULL参数,但这绝不是保证。

如果你的函数依赖于由NULL参数发出信号的变量参数列表的末尾,那么它依赖于调用者来提供一个。很可能在你的参数列表中没有空终止会引起你所询问的行为。