我使用以下模式为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
答案 0 :(得分:2)
你似乎对可变参数函数有一些误解,例如这种计算变量参数的方法:
while (va_arg(argList, NSObject *)) { argCount += 1; }
该代码假定变量参数至少有一个成员,所有成员都是NSObject *
类型,并且该列表将由该类型的空指针终止。这些都不受系统保证,如果不满足这些假设,那么一个或多个va_arg()
调用的行为将是未定义的。
在实践中,您可能可以使用其他类型指针的实际参数(虽然在形式上,在这种情况下仍然未定义行为)。但是,如果参数可能具有非指针类型,那么计算它们的方法就完全被打破了。更重要的是,您的测试用例似乎假设系统将提供一个尾随NULL
参数,但这绝不是保证。
如果你的函数依赖于由NULL参数发出信号的变量参数列表的末尾,那么它依赖于调用者来提供一个。很可能在你的参数列表中没有空终止会引起你所询问的行为。