1

有时我想返回一个数组,我知道我应该做什么让调用者制作数组并在方法中修改数组。但事实证明这行得通?

@interface Test : NSObject
@end

@implementation Test

- (CGPoint[2])test {
    CGPoint p1 = {1, 2};
    CGPoint p2 = {3, 4};
    return (CGPoint[2]) {p1, p2};
}

- (int[2])test2 {
    int i = 1;
    int i2 = 2;
    return (int[2]){i, i2};
}

- (int[5])test3 {
    int i = 1;
    int i2 = 2;
    return (int[5]){i, i2, 3, 4, 5};
}

@end

@implementation testTests

- (void)testExample
{
    Test *t = [Test new];
    CGPoint p = [t test][0];
    CGPoint p2 = [t test][1];
    CGPoint *ps = [t test];
    CGPoint p3 = ps[0];
    CGPoint p4 = ps[1];
    NSLog(@"%@ %@ %@ %@", NSStringFromCGPoint(p), NSStringFromCGPoint(p2), NSStringFromCGPoint(p3), NSStringFromCGPoint(p4));
    // {1, 2} {3, 4} {1, 2} {3, 4}

    {
        Method m = class_getInstanceMethod([Test class], @selector(test));
        const char *rettype = method_copyReturnType(m);
        NSUInteger size = 0;
        NSGetSizeAndAlignment(rettype, &size, NULL);
        NSLog(@"%s %d", rettype, size); // [2{CGPoint=ff}] 16
    }

    {
        Method m = class_getInstanceMethod([Test class], @selector(test2));
        const char *rettype = method_copyReturnType(m);
        NSUInteger size = 0;
        NSGetSizeAndAlignment(rettype, &size, NULL);
        NSLog(@"%s %d", rettype, size); // [2i] 8
    }

    {
        Method m = class_getInstanceMethod([Test class], @selector(test3));
        const char *rettype = method_copyReturnType(m);
        NSUInteger size = 0;
        NSGetSizeAndAlignment(rettype, &size, NULL);
        NSLog(@"%s %d", rettype, size); // [5i] 20
    }
}

@end

所以看起来我可以直接返回一个数组,比如返回一个结构。正如所NSGetSizeAndAlignment建议的,返回值实际上是整个数组,而不仅仅是一个指针。(16 个CGPoint[2],8个int[2],20 个int[5]

但是要实际使用返回值,我只能这样做

CGPoint *ps = [t test];

这里ps只是一个指针。所以问题是我可以像这样安全地返回数组吗?数组分配在哪里?在被调用函数堆栈中还是在调用函数堆栈中?整个数组是从被调用函数复制的,还是像 struct 编译器自动在调用函数中分配数组一样是真的吗?

我正在使用 Xcode 4.6.2


更新

看起来这只适用于 32 位应用程序,其中包括 iOS 应用程序 (ARM)、iOS 模拟器 (x86) 和 32 位 OSX 应用程序。

4

1 回答 1

1

C 不支持返回数组。为什么这显然对你有用,我不知道。我输入了您的代码,添加了缺少的方法/导入,修复了格式警告,调整了第一个NSLog- 这些都没有以任何重要的方式改变代码 - 结果是:

p: {0.000000, 0.000000}
p2: {49923903424063506281151950574939686583391145211556805465272890158614887709849874883833083042335634395057539888815696938380880844447770099086084465717389090095104.000000, 5053651901346670085892443969395884608467030378650281488576308318306304.000000}
p3: {0.000000, 0.000000}
p4: {0.000000, 0.000000}

简而言之,垃圾——这是你所期望的。

那是在 10.8.4 上使用 Apple LLVM 默认编译器的 Xcode 4.6.2。切换到 gcc-llvm 编译器,它甚至不会编译 - 错误是方法无法返回数组。为了完整起见,还测试了 C11 语言选项 (Apple LLVM) - 如上所述运行并产生垃圾。

如果您确实需要按值传递数组,您可以使用一个简单的技巧来做到这一点:原始类型(int、float 等)和结构都在 C 中按值传递,因此您可以通过包装来按值传递数组一个struct

typedef struct
{
   CGPoint values[2];
} Array2;

@interface Test2 : NSObject
@end

@implementation Test2

- (Array2)test
{
   CGPoint p1 = {1, 2};
   CGPoint p2 = {3, 4};
   return (Array2) {{p1, p2}};
}


- (void)testExample
{
   Test2 *t = self;
   CGPoint p = [t test].values[0];
   CGPoint p2 = [t test].values[1];
   Array2 ps = [t test];
   CGPoint p3 = ps.values[0];
   CGPoint p4 = ps.values[1];
   NSLog(@"p: %@\np2: %@\np3: %@\np4: %@", NSStringFromCGPoint(p), NSStringFromCGPoint(p2), NSStringFromCGPoint(p3), NSStringFromCGPoint(p4));
}

@end

这会产生:

p: {1.000000, 2.000000}
p2: {3.000000, 4.000000}
p3: {1.000000, 2.000000}
p4: {3.000000, 4.000000}

正如它应该。

附录 - 对评论的回应

C 标准不允许返回数组(和函数)——但允许引用数组(和函数)。

对于函数参数,如果给定的类型是数组(或函数)类型,则将其调整为表示对数组(或函数)的引用。

虽然我不知道标准说明了这一点,但一些编译器(例如 Apple 4.2,但不是 GCC 4.2)也将参数类型调整应用于返回类型 - 因此您可以声明一个函数来返回一个数组并对其进行调整作为对数组的引用。

Objective-C 的元数据记录了声明的类型,而不是调整后的类型。这就是为什么您在调用method_copyReturnType/时会看到结果NSGetSizeAndAlignment。但是编译遵循标准,调整类型,而不是按值返回数组。

话虽如此,编译器当然可以选择将 array-by-value 实现为扩展——因为这样做的机器无论如何都需要支持 struct-by-value。我不知道是否有任何编译器支持这样的扩展。我只测试了 4.2 Intel 编译器(Apple 和 GCC),但它们没有(而且 GCC 也不支持将返回类型声明为数组,这是一个编译时错误)。

高温高压

于 2013-06-13T20:45:15.967 回答