Search code examples
iosobjective-ciphonexcodeblock

why Complie Error only when select x86_64 simulator "Cannot refer to declaration with an array type inside block"


我的问题是:为啥仅在使用 x86_64 编译时会出现此编译错误?以及如何解决?
My question is: why only when select x86_64 simulator compile error occurs? And how to solve?

环境: Xcode v8.3.2
测试代码:

+ (BOOL)updateSqlByFileName:(NSString *)file key:(NSString *)key, ...
{
    va_list args;
    va_start(args, key);

    __block BOOL isOK = NO;
    [_queue inDatabase:^(FMDatabase *_dataBase)//
    {
        isOK = [_dataBase executeUpdate:sql withVAList:args];
    }];

    va_end(args);
    return isOK;
}
不同的编译方式,编译错误情况
  • 【编译正常√】选择 Generic iOS Device 编译(Build)时:【armv7 + arm64】

    CompileC Test.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    CompileC Test.m normal arm64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    
  • 【编译正常√】 选择真机: iPhone 4(7.1.2)编译(Build)时:【armv7】

    CompileC Test.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    
  • 【编译错误×】选择模拟器: iPhone 5s(10.3),iPhone SE(10.3) ,iPhone 7 Plus(10.3)编译(Build)时:【x86_64】

    CompileC Test.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    

    Complie Error:
    Cannot refer to declaration with an array type inside block

  • 【编译正常√】选择模拟器: iPhone 5(10.3)编译(Build)时:【i386】

    CompileC Test.m normal i386 objective-c com.apple.compilers.llvm.clang.1_0.compiler
    

Solution

  • If a block captures a non-__block variable, a copy of the variable is made when the block is created and stored within the block. Arrays cannot be assigned in C, and that may be why the designers of blocks disallowed capturing variables of array type in a block.

    What kind of type va_list is is not specified by the C standard; it is implementation-specific. It could be implemented as an array type, a pointer type, a struct type, or whatever, and this can be different across architectures on the same compiler. Probably they implemented it as an array type in x86_64 and as some non-array type on the other 3 architectures. There is nothing unusual about this. You cannot make assumptions about what kind of type va_list is.

    In your answer where you defined another function and passed the va_list to it, that happens to work in the case where va_list is an array type, because C automatically adjusts any parameters of type "array of T" to "pointer to T", so the parameter args in the method +foo:key:withVAList: actually has a pointer type when va_list is an array type (different type than the args in the +foo:key: method), and pointer variables can be captured into a block fine.

    An alternative solution would be to take the address of the va_list, getting a va_list * and putting that into a variable that is used in the block. This is guaranteed to be a pointer type no matter what va_list is, and can be captured in blocks fine. When you need to use the actual va_list inside the block you can dereference the pointer. For example, something like this:

    + (BOOL)updateSqlByFileName:(NSString *)file key:(NSString *)key, ...
    {
        va_list args;
        va_start(args, key);
        va_list *ptrToArgs = &args;
    
        __block BOOL isOK = NO;
        [_queue inDatabase:^(FMDatabase *_dataBase)
        {
            isOK = [_dataBase executeUpdate:sql withVAList:*ptrToArgs];
        }];
    
        va_end(args);
        return isOK;
    }