简体   繁体   English

可变参数函数缓存上次调用的参数列表

[英]Variadic Function caches argument list of last calls

I wrote a Helper class with c functions for an iOS Library with the following pattern. 我使用以下模式为iOS库编写了带有c函数的Helper类。 There are 2 wrapping (variadic) functions, which finally call the same function, with slightly different parameter. 有2个包装(可变)函数,它们最终调用相同的函数,但参数略有不同。 Idea is to have "default" properties being set. 想法是设置“默认”属性。

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

Both will then call the following function: 两者都将调用以下函数:

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

Implementation as followed: 实现如下:

__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);
        }
    ...
}

My Unit Tests for the function look the following (order is important). 该功能的“我的单元测试”如下所示(顺序很重要)。

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

The crash happens when I want to use the arguments and the format (making format strong did not solve the problem, and does not seem being part of the problem, see below): 当我想使用参数和格式时(当使格式变强不能解决问题,并且似乎不是问题的一部分时,请参见下文),会发生崩溃:

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

Here is the Log: 这是日志:

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

Of course we won't see the desired string "2 Placeholders. 0 Vars %@ --- %@" as the crash happened before. 当然,我们不会看到所需的字符串"2 Placeholders. 0 Vars %@ --- %@"因为崩溃之前已经发生。

So, the question is now: Why is the amount of arguments now being 4 instead of 0? 所以,现在的问题是:为什么参数的数量现在是4而不是0? As none being passed in the second call, are the arguments being collected when the function is being called immediately again? 由于在第二次调用中没有传递任何参数,因此在再次立即调用该函数时是否收集了参数?

So, I started to call the function "again" to make sure the argument's list is being cleared, although va_end was being called: 因此,尽管调用了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);
}

This does work now like a charm (argument's list is being cleared and the desired output is being received): 现在确实可以像超级按钮一样工作(正在清除参数列表并已接收到所需的输出):

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 %@ --- %@

Here is finally my question: 最后是我的问题:

What is the reason for this behavior and how can I avoid it? 出现这种现象的原因是什么,我该如何避免呢? Is there a better way to solve the issue than "stupidly" calling the function a second time with "no" arguments to clear the them? 有没有比“愚蠢”第二次调用带有“ no”参数来清除函数的函数更好的解决此问题的方法? Ps I tried not to use macros, because I consider them as more error prone than c functions. ps我试图不使用宏,因为我认为它们比c函数更容易出错。 See this thread: Macro vs Function in C 看到这个线程: C语言中的Macro vs Function

You appear to have some misconceptions about variadic functions, exemplified by this approach to counting the variable arguments: 您似乎对可变参数函数有一些误解,这种计算可变参数的方法可以证明这一点:

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

That code assumes that the variable arguments have at least one member, that all of them are of type NSObject * , and that the list will be terminated by a null pointer of that type. 该代码假定变量参数具有至少一个成员,并且所有参数均为NSObject *类型,并且该列表将由该类型的空指针终止。 None of those is guaranteed by the system, and if those assumptions are not satisfied then the behavior of one or more va_arg() invocations will be undefined. 这些都不是由系统保证的,如果不满足这些假设,则一个或多个va_arg()调用的行为将是不确定的。

In practice, you can probably get away with actual arguments that are pointers of other types (though formally, the behavior will still be undefined in that case). 在实践中,您可能可以避免使用其他类型的指针作为实际参数(尽管在形式上,行为在这种情况下仍将是未定义的)。 If the arguments may have non-pointer types, however, then that approach to counting them is completely broken. 但是,如果参数可能具有非指针类型,则完全无法使用这种方法对它们进行计数。 More importantly, your test cases appear to assume that the system will provide a trailing NULL argument, but that is in no way guaranteed. 更重要的是,您的测试用例似乎假定系统将提供尾随NULL参数,但这绝不能保证。

If your function relies on the end of the variable argument list being signaled by a NULL argument, then it is relying on the caller to provide one. 如果您的函数依赖于由NULL参数发出信号的变量参数列表的末尾,则它是依靠调用者提供的。 It is very likely the absence of null termination in your argument lists that gives rise to the behavior you are asking about. 您的参数列表中很可能没有空终止符,这会引起您要询问的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM