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は単なるポインタです。問題は、このような配列を安全に返すことができるかということです。配列はどこに割り当てられますか? 呼び出し先関数のスタック内または呼び出し元関数のスタック内? 配列全体が呼び出し先関数からコピーされるか、コンパイラーが呼び出し元関数で配列を自動的に割り当てた構造体のようにコピーされるのは本当ですか?

Xcode 4.6.2 を使用しています


アップデート

これは、iOS アプリ (ARM)、iOS シミュレーター (x86)、および 32 ビット OSX アプリを含む 32 ビット アプリでのみ動作するようです。

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}

要するに、ガベージ - これはあなたが期待するものです。

これは、Apple LLVM のデフォルト コンパイラを使用する 10.8.4 上の 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 標準では、配列 (および関数) を返すことは許可されていませんが、配列 (および関数) への参照は許可されています。

関数パラメーターの場合、指定された型が配列 (または関数) 型の場合、配列 (または関数) への参照を意味するように調整されます。

標準に記載されているかどうかはわかりませんが、一部のコンパイラ (GCC 4.2 ではなく Apple 4.2 など) は、パラメーターの型調整を戻り値の型にも適用するため、配列を返す関数を宣言すると調整されます。配列への参照になります。

Objective-C のメタデータは、調整された型ではなく、宣言された型を記録しています。これが、 /を呼び出したときに実行した結果が表示される理由です。ただし、コンパイルは標準に従っており、型を調整しており、配列を値で返していません。method_copyReturnTypeNSGetSizeAndAlignment

もちろん、コンパイラは値による配列を拡張として実装することを選択できると言いました-とにかく値による構造体をサポートするためにそのための機械がそこにある必要があるためです。コンパイラがそのような拡張機能をサポートしているかどうかはわかりません。私は 4.2 Intel コンパイラ (Apple & GCC) のみをテストしましたが、テストしていません (GCC は戻り値の型を配列として宣言することもサポートしていません。これはコンパイル時エラーです)。

HTH

于 2013-06-13T20:45:15.967 に答える