2017-10-17 33 views
2

我使用以下模式为iOS库编写了一个具有c函数的Helper类。 有2个包装(可变参数)函数,最终调用相同的函数,参数略有不同。想法是设置“默认”属性。然后Variadic函数缓存最后调用的参数列表

__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?由于没有在第二次调用中传递,函数被立即再次调用时是否收集了参数?

于是,我开始为“一次”调用的函数,以确保参数的列表被清除,尽管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 %@ --- %@ 

这里是我的最后一个问题:

这种行为的原因是什么?我该如何避免这种行为?有没有更好的方法来解决这个问题,而不是“愚蠢地”第二次用“无”参数调用函数来清除它们? P.s.我试图不使用宏,因为我认为它们比c函数更容易出错。看到这个线程:Macro vs Function in C

+1

问问你自己:如何'va_arg'知道什么时候停止? –

+0

这似乎是通过实施可选的第一个参数来解决很多麻烦的问题。如何,而不是。 –

+0

@JohnBollinger谢谢你在约翰。为了简单起见,我刚刚提到了两个带有一个可选参数的函数。当然,还有更多。 – Lepidopteron

回答

2

你似乎有关于可变参数的功能,通过这种方法例证一些误解,以计数的变量参数:

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

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

在实际应用中,你也许可以蒙混过关是其他类型的指针(尽管形式上,该行为仍然会在这种情况下,未定义)的实际参数。但是,如果参数可能有非指针类型,那么对它们进行计数的方法就完全失效了。更重要的是,你的测试用例似乎认为系统将提供尾随NULL的说法,但那是在没有办法保证。

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

+0

谢谢约翰澄清。这个函数应该在一个库中使用,因此我想确保用户不会崩溃他们的代码,如果他们没有附加预期的sentinel值。在调用包装函数'prefixAndArguments'之前,可以在参数的列表中追加一个这样的参数吗? – Lepidopteron

+0

不,@Lepidopteron,不能。但是,可以想象,为包装真实函数的用户提供类似于函数的*宏,并将'(NSObject *)NULL'附加到用户的参数列表中。但是,如果传递了非指针类型的可变参数,那么它仍然无法保护您免受不当行为的影响。变量功能给用户带来实质性的责任以传递适当的论据;编译器无法检查它们。 –

+1

@Lepidopteron如果使用宏'NS_REQUIRES_NIL_TERMINATION'未正确终止列表,则可能会收到编译器警告。但是,正如约翰指出的那样,**只有在所有参数都是指针类型**时才有效。如果你期望原始数据被传递(看起来很可能),你必须获得要读取的项目数。这可以通过计算函数中的格式说明符来实现(这就是'printf' /'stringWithFormat:'做到这一点),或者通过添加一个明确的计数参数。 –

相关问题