memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

objective-cの引数渡し

※以下、seino様のコメントをもとに大幅に訂正。

objective-cC言語オブジェクト指向化するために拡張した言語と言われています

iphoneアプリを書いていたときに、あるメソッドに引数をポインタ渡しし、そのメソッドで値を書き換えて、呼び出し元に戻ってくると思い込んでいたのですがうまくいかない。。。なぜだろう?と思いこのエントリーを書き始めました。

結論を先に書きますと、seino様のご指摘の通り、ポインタ変数にはアドレスが格納されているものの、ポインタ変数自体は呼び出し元と呼び出し先では実体が異なるため(要は値渡し)、呼び出し先で内容を変更しても呼び出し元では変わらない、ということでした。

以下、seino様のご指摘を確認してみた次第です。この点においてはC言語objective-cは同じでした。

以下、C言語の例

#include <stdio.h>

void set(char *arg) {
 printf("%p,%p\n", arg, &arg);
}

int main(void) {
 char *s = "hello";
 printf("%p,%p\n", s, &s);
 set(s);
}

■実行結果
0x104ffcf27,0x7fff64bfb790
0x104ffcf27,0x7fff64bfb768
↑ポインタ変数の中身は同一、しかしポインタ変数自体のアドレスは別

以下、objective-cの例

#import <Foundation/NSObject.h>
#include <stdio.h>

@interface Test : NSObject
-(void)set:(NSString*) arg;
@end

@implementation Test
-(void)set:(NSString*) arg {
  printf("%p,%p\n", arg, &arg);
}

@end

int main(void) {
  Test *t = [[Test alloc] init];
  NSString *s = @"hello";
  printf("%p,%p\n", s, &s);
  [t set:s];
}

■実行結果
0x104f73198,0x7fff64b71770
0x104f73198,0x7fff64b71738
↑ポインタ変数の中身は同一、しかしポインタ変数自体のアドレスは別

ちなみに、objective-cの特徴の1つであるid(←そもそもこれがよくわからなかったのでこのエントリーを書き始めたのですが昨日、詳解 Objective-C 2.0を読んだところ「void*と同じと考えればよい」と書かれていて納得した次第でした)に書き換えたのが以下です。実行結果は同様でした。

#import <Foundation/NSObject.h>
#include <stdio.h>

@interface Test : NSObject
-(void)set:(id) arg;
@end

@implementation Test
-(void)set:(id) arg {
  printf("%p,%p\n", arg, &arg);
}

@end

int main(void) {
  Test *t = [[Test alloc] init];
  NSString *s = @"hello";
  printf("%p,%p\n", s, &s);
  [t set:s];
}

ちなみに、javaについても調べてみました。javaを勉強し始めたころにあった書籍にはよく「javaにはポインタはない」と書かれていたことを思い出します。それを読んで余計によくわからなくなったものです。それはいいとして、以下、クラスBを作成して、そのインスタンスを引数として渡したときにそのアドレスがどのように変化するか見てみたものです。上記のポインタ変数によるアドレスの値渡しをイメージできれば以下の動作も理解できます。

以下、javaの例

class B{} 

class A { 
  public void set(B b) {
    System.out.println("2:"+b);
    b = new B();
    System.out.println("3:"+b);
  }
  public static void main(String [] args) {
    A a = new A();
    B b = new B();
    System.out.println("1:"+b);
    a.set(b);
    System.out.println("4:"+b);
  }
}

■実行結果
1:B@4b71bbc9
2:B@4b71bbc9
3:B@17dfafd1
4:B@4b71bbc9

ついでに、アドレス渡しで内容を書き換える場合は、例えば配列だと以下のような感じになりますね。配列変数のアドレス自体は呼び出し元でも呼び出し先でも変わりませんが、そのアドレスをもとに内容を書き換えます。別の言い方をすれば呼び出し元と呼び出し先でインスタンス自体を変えることはできないけどインスタンスの内容は書き換えることができる、というところでしょうか。

class B{} 

class A { 
  B bb = new B();
  public void set(B[] b) {
    System.out.println("2:"+b[0]);
    b[0] = new B();
    System.out.println("3:"+b[0]);
  }
  public static void main(String [] args) {
    A a = new A();
    B[] b = new B[1];
    b[0] = new B();
    System.out.println("1:"+b[0]);
    a.set(b);
    System.out.println("4:"+b[0]);
  }
}

■実行結果
1:B@2c6f7ce9
2:B@2c6f7ce9
3:B@4b71bbc9
4:B@4b71bbc9

情けない話しでしたが、よい勉強の機会でした。

※以下、私の勘違いの元エントリーです。

参考:一番初めのObjective-Cプログラム