NSStringオブジェクトとポインター
次のコードは何を出力すると思いますか?
#import "Foundation/Foundation.h" int main(){ @autoreleasepool { NSString *a = @"123456789"; NSString *b = a; NSLog(@"%p %p", a, b); } return 0; }
当然、ポインタは等しい(「オブジェクトは参照によって割り当てられる」)ため、NSLog()は2つの同一のメモリアドレスを出力します。 魔法なし:
2015-01-30 14:39:27.662 1 nsstring [13574] 0x602ea0 0x602ea0
以下、オブジェクトのアドレスを例として示します。 再現しようとすると、実際の値はもちろん異なります。
同じテキストを持つ2つの異なる NSStringがあることを確認してみましょう。 NSArrayなどの他の標準クラスの場合、次のように記述できます。
#import "Foundation/Foundation.h" int main(){ @autoreleasepool { NSArray *a = @[@"123456789"]; NSArray *b = @[@"123456789"]; NSLog(@"%p %p", a, b); } return 0; }
NSArrayを個別に初期化したため、それらはメモリの異なるセクションに配置され、コンソールに2つの異なるアドレスが表示されます。
2015-01-30 14:40:45.799 2-nsarray [ 13634 ] 0xa9e1b8 0xaa34e8
ただし、同じ方法をNSStringに適用しても、目的の効果は得られません。
#import "Foundation/Foundation.h" int main(){ @autoreleasepool { NSString *a = @"123456789"; NSString *b = @"123456789"; NSLog(@"%p %p", a, b); } return 0; }
2015-01-30 14:41:41.898 3-nsstring [13678] 0x602ea0 0x602ea0
ご覧のとおり、個別の初期化にもかかわらず、両方のポインタは同じメモリ領域を参照しています。
stringWithStringを使用する
NSStringを少し掘り下げると、「別の指定された文字列から文字をコピーして作成された文字列を返す」 stringWithStringメソッドが見つかります。 これが必要なものです! 次のコードを試してみましょう。
#import "Foundation/Foundation.h" int main(){ @autoreleasepool { NSString *a = @"123456789"; NSString *b = [NSString stringWithString:@"123456789"]; NSString * = [NSString stringWithString:b]; NSLog(@"%p %p %p", a, b, ); } return 0; }
このプログラムの出力は、使用するコンパイラのバージョンに依存することがわかります。 したがって、LLVM 3.4のUbuntuでのclangは、実際には異なるメモリロケーションにある3つの異なるオブジェクトを作成します。 ただし、LLVM 3.5でclang for Macを使用してXcodeで指定されたコードをコンパイルすると、 1つのオブジェクトとそれへの3つのポインターのみが生成されます。
2015-01-30 17:59:02.206 4-nsstring [670:21855] 0x100001048 0x100001048 0x100001048
マジックセッションの公開
上記の奇妙な点は、文字列リソースを最適化しようとするコンパイラの試みによって説明されています。 ソースコード内の同じコンテンツを持つ文字列オブジェクトを満たし、それらを一度だけ作成して、ストレージと比較のコストを節約します。 この最適化は、リンク段階でも実行されます。同じテキストの行が異なるモジュールにある場合でも、ほとんどの場合、一度だけ作成されます。
NSString型は不変であるため(NSMutableStringは可変文字列に使用されます)、この最適化は安全です。 NSStringクラスのメソッドで文字列のみを操作する限り。
ただし、コンパイラは全能ではありません。 それを混乱させ、同じテキストで2つの異なるNSStringを実際に作成する最も簡単な方法の1つは次のとおりです。
#import "Foundation/Foundation.h" int main(){ @autoreleasepool { NSString *a = @"123456789"; NSString *b = [NSString stringWithFormat:@"%@", a]; NSLog(@"%p %p", a, b); } return 0; }
Gcc
Gccは、Cコードのコンパイル時に同様の文字列定数の最適化を実行します。 例えば
#include <stdio.h> void main(){ char *a = "123456789"; char *b = "123456789"; printf("%p %p\n", a, b); }
0x4005f4 0x4005f4を出力します。
ただし、clangとの間に大きな違いがあります。gccはこのような文字列定数を読み取り専用セグメントに配置します-実行時に変更しようとすると(たとえば[0] = '0')、セグメンテーションエラーが発生します。 変更可能なスタックに行を配置するには、char * aをchar a []に置き換える必要がありますが、この場合、gccは最適化を適用しません。 次のコードは、すでに2つの異なる行を作成します。
#include <stdio.h> void main(){ char a[] = "123456789"; char b[] = "123456789"; printf("%p %p\n", a, b); }
0x7fff17ed0020 0x7fff17ed0030
足射
そのため、ソースコード内の同じ文字列オブジェクトを満たすと、コンパイラはそれらを最適化し、NSStringを1回だけ作成します。 同時に、彼はそれをヒープ内に作成し、ポインタを使用して手動で操作することで変更できます。 (上で説明したプレーンCでは、これは不可能です。)
次のコードが何を印刷するかを推測してください
#import <Foundation/Foundation.h> void bad(){ NSString* a = @"123456789"; char* aa = (__bridge void *)(a); aa[8] = 92; } int main(){ @autoreleasepool { bad(); NSLog(@"%@", @"123456789"); } return 0; }
コンパイラーに応じて、結果は異なる場合があります。私のXcodeはMacの下で9 ofのセットを出力し、Ubuntuのclangはred:parsサービス情報からのフラグメントを出力します。 いずれにせよ、これは期待される「123456789」ではありません。 aa [8]およびaa [16]の他の値の実験では、読者が自分でそれを行うことをお勧めします。
最悪の部分は、最後の例のbad()関数をヘッダーの後ろに配置できることです。たとえば、自分のニーズに応じて(見たところ)NSStringを変更した別の著者のプラグインライブラリにあります。 スマートコンパイラは、一致する文字列定数を見つけて1つのポインターに閉じます。その後、変数をbad()内で台無しにすると、メイン()コンテキストの文字列が象形文字に変わります。