光学薄膜設計ソフトの設計 その27 - 複素数を返すメソッドの問題 [考え中 - 光学薄膜設計]
前回C99でサポートされた複素数型を使うことにした。Objective-CはC99の拡張にも互換性が維持されている、ということなのでメソッドによっては複素数型を返すこともありえる。これに問題が発生してずいぶん悩んだ。
C99複素数型とObjective-Cメソッドとの共存
こんな複素数型を返すメソッドを書いたとする。
#import <Foundation/Foundation.h> #include <complex.h> @interface TComplex : NSObject { double complex val; } - (double complex)cvalue; @end @implementation TComplex - (id)init { self = [super init]; val = 1.0 + 1.0 * I; return self; } - (double complex)cvalue { return val; } @endこれを
int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; TComplex *var = [[TComplex alloc] init]; double complex a = [var cvalue]; // (1) printf("[%f, %f]\n", creal(a), cimag(a)); [pool drain]; return 0; }のように呼んだ。これがIntel iMacでは(1)のとことでクラッシュする。どうやら間違ったメソッド実装が呼ばれるらしく、EXC_BAD_ACCESSエラーで終わる。この部分の前にいろいろ付け加えるとメソッドが見つからない、という例外発生に変わったりする。でも実行できないことには変わりがない。
これを実数を返すメソッドにすると問題ない。それは当然。
どうもメソッドはC99複素数型を返せないのか、ひどいじゃん、と半分あきらめていたら、PowerBook G4で同じソースをPowerPCのコードとしてコンパイルして実行すると問題ない。
こういうのやだなあ。いろいろ試してみるとiMacでもコンパイラをgcc4.0から4.2に変更するとちゃんと動くようになった。
ああ、だめだ、こういうの。一気にコンパイラ(なのかそれともランタイムとの関係の問題なのか)に対する信頼が揺らいでしまう。
@synchronizedのあとのsuperのメソッド呼び出しへの警告
ほかにも不思議な挙動がある。これはエラーではないのだけど
static int varnum = 0; - (id)initWithIncrenment { int varnumcopy; self = [super init]; @synchronized(NSStringFromSelector(_cmd)) { varnumcopy = varnum ++; // (2) } ........ [super setDependOn:YES]; // (3)のようにファイルの静的変数を(2)インクリメントしているので、@synchronizedで囲んで他のスレッドから同時に実行されて値のコピーとインクリメントがセットで必ず実行されるようにした。ようするにインスタンスにuniqueな整数を振りたい、ということである。
ちなみに@synchronizedはこのブロックが、あるスレッドで実行されているとき別のスレッドで同じコードを実行しようとすると、後のスレッドは先のスレッドがこのブロックを抜けるまでロックされる。つまり、このブロックは必ずひとつのスレッドでしか実行できないようにすることができる。正確には@synchronizedの引数にとったオブジェクトが一致しているスレッドだけがロックされる。
この場合、もし@sychronizedで囲まれていなかったら、たまたまふたつのスレッドでこのメソッドを実行して、一方が値のコピーをしてから次にインクリメントする前に、もう一方がコピーをしてしまったら、同じ値のコピーを違うオブジェクトが持つことになる。
ここでの@syncrhonizedの引数はNSStringFromSelector(_cmd)というFoundation.Frameworkにある関数を使っている。これはセレクタに対応した文字列を返す。したがって同じメソッドinitWithIncrenmentを実行しようとする複数のスレッドは排他的に実行される(はずである)。詳細はここに。
これを入れて、この後の部分で(3)のように親クラスのメソッドを呼ぼうとするとコンパイラは
warning: invalid receiver type '_objc_super *'という警告を出す。一番上の親のinitを呼ぶところでは何も言わないので@synchronizedブロックの後が問題らしい。
実際この警告を無視して実行しても、ちゃんと親のメソッドが呼ばれて問題は発生しない。自分で(3)のメソッドを上書きしていなかったら
[self setDependOn:YES]; // (4)としても全く同じのはずなのに、今度は警告が出ない。これは何を意味してるんだろう?
ちなみに@synchronizedではなくて自前でロックしてみると
static int varnum = 0; - (id)initWithIncrenment { int varnumcopy; self = [super init]; NSLock *lock = [[NSLock alloc] init]; [lock lock]; varnumcopy = varnum ++; [lock unlock]; [lock release]; ........ [super setDependOn:YES]; // (5)これは何も言わないしもちろんちゃんと動く。当然@synchronizedの方が簡単なのでこっちを使いたい。でもこの警告は気持ち悪い。
さらにちなみにThread Programing guideでは@synchronizedブロックには例外処理も含まれているのでそのオーバーヘッドが嫌ならNSLockを使え、と書いてある。でも親クラスのメソッドの呼び出しのことは何も書いていない。
こういう問題は時間を空費する。結局どっちの問題もよくわからなかった。ジジイはただでさえ時間が少ないのに、まったく、もう。
コメント 0