2013-04-09 2 views
8

私は一緒にどのように生成されたアセンブリとランタイムの仕事を理解することが好きで、いくつかの生成されたアセンブリコードをステップ実行しながら、質問に出くわしたでしょう。生成されたアセンブリで、コンパイラとランタイムシステムは実際に何をしましたか?

ソースの例

ここ3 XCodeの4.5で実行しているのObjective-Cの行、以下のとおりです。生成されたアセンブリを介して生成されたアセンブリ

ステッピングを比較

// Line 1: 
NSObject *obj1 = [[NSObject alloc] init]; 

// Line 2: 
[obj1 release]; 

// Line 3: 
NSObject *obj2; 

は、Iいくつかの観察をした。

は、ライン1の前に、obj1のアドレスとして示されている:

obj1 (NSObject*) 0x00003604 

は、ライン1の後、それが変化:

obj1 NSObject * 0x08122110 

観察

1)obj1のアドレスをかわった。ソースコードがコンパイルされると、コンパイラはobj1のメモリを一時的に割り当てます。次に、(1行目以降)コンパイラは明らかにオブジェクトのアドレスが変更されるように再割り当てします。 2行目の後

2)は、obj2のアドレス)が0x08122110(まだ同じです! [obj1 release]と呼ぶと、私はコンパイラーにこう言っています。「もうこれは必要ありません。取り除いてください。しかし、システムは実際には将来、ある時点でリリースを行っており、私はそれを直接制御することはできません。

3)デバッガは、なぜそれがないだろう、私は理解していないライン3ステップオーバーすることはできません!オブジェクトの作成および破壊の点で

質問

、コンパイラは、実際に割り当てずに、コードのこれらの行(具体的には「ALLOC-INIT」、リリース、およびNSObjectのポインタ宣言とは何をしているのです)?また、なぜデバッガは私に3行目を踏ませませんか?デバッガには表示されませんか?

答えに加えて、コンパイラと実行時システムが実際に何をしているかについていくつかのドキュメントや書籍をお薦めしたいなら、私はそれを感謝します。どうもありがとうございました!

+8

これがなぜ閉鎖されたのか分かりません。 「なぜデバッガは私にこの行を乗り越えさせてくれないのですか?」というのは、多くの新しい開発者にとっては、それ自体で、やりがいのある点です。 「何かをする表現」を理解することは、「この行は純粋にあなたの[利便性のために]あり、実際にはあまり役に立たない」ということは非常に便利です。一般的に「コンパイラはこれで何を行い、どのようにランタイムとやりとりするのか」という一般的なことも非常に面白いですが、ほとんどの開発者はほとんどの場合、その詳細はほとんど必要ありません。 – bbum

+0

なぜ私は3行目を越えることができないのか分かりません^^ – DungProton

+2

3行目はステートメント( "これを行う")ではないので、宣言( "this exists")です。ステッピングはステートメント間を移動します。 1行目のようなイニシャライザを使った宣言は、ステッピングの目的には十分に近いです。なぜなら、イニシャライザは何かをすることです。何かを変数に入れて、1行目のイニシャライザもオブジェクトを作成します。そこに置く。 –

答えて

12
  1. obj1というポインタがスタック上に作成されます。それは初期化されていません。つまり、そのメモリ位置にあったものがすべて格納されます。これは、初期化されていないポインタを使用すると、不特定の動作につながる可能性があるため、絶えずバグの原因となります。オブジェクトが割り当てられると、ポインタはそのアドレスで初期化されます。

  2. ポインタが更新されないため、アドレスが変更されません。 -releaseメッセージがオブジェクトに送信されるとき、保持カウンタは通常1だけ減少します。保持カウンタがすでに1になっている場合は、-deallocメソッドが呼び出され、メモリは空きとしてマークされます。ポインタが指すメモリだけが空きとしてマークされますが、ポインタは同じままです。そのため、ポインタをnilに設定することを好む人もいます。

  3. 初期化されていないポインタを作成しています。初期化されていないので、既にポインタが格納されているメモリ位置にあったデータが再利用されます。

本の推薦について。 コンパイラ:原則、テクニック、ツールをお勧めします。

+0

3行目に 'NSObject * obj2'と書いてみました – DungProton

+0

そして、私はApple(XCode)が使用したコンパイラとランタイムシステムについて言及したかったのです! – DungProton

+1

ARCは、すべてのオブジェクト参照変数がnilに初期化されることを保証します。 – bbum

13

マーカスの答えはかなり良いですが、ここではいくつかの詳細があります(私は生成されたアセンブリを読み上げることを意味していました;実際に試して説明することが最善の方法です)。

NSObject *obj1 = [[NSObject alloc] init]; // Line 1 

コンパイラは2つの機能がobjc_msgSend()に呼び出しをコンパイルします。最初のクラスは、NSObjectクラスの+allocメソッドを呼び出します。その関数呼び出しの結果は、メソッド-initを呼び出す2番目の関数呼び出しの最初の引数、つまり対象オブジェクトになります。

initを呼び出した結果

は、あなたがNSObjectののインスタンスへ ポインタの型を持っているobj1を命名されていると宣言したメモリの塊にスタック上に保存されています。

行に実行された式があるため、デバッガでこの行を実行することができます。コードが次のように書かれている場合:

NSObject *obj1; // declaration 
obj1 = [[NSObject alloc] init]; 

次に、宣言を進めることはできません。

前にobj1 = [[NSObject alloc] init];, the value of obj1 is *undefined* under Manual Retain Release, but **will be automatically set to nil`(0)の下でARC **(これにより、Marcusが示したバグの原因が排除されます)。

[obj1 release]; // Line 2 

この行はNSObjectののインスタンスにrelease方法はobj1により指さ呼び出します。

NSObject *obj2; // Line 3 

この行は効果的に何もしません。コンパイラのオプティマイザがオンになっている場合、コードはまったく生成されません。オプティマイザがなければ、コンパイラsizeof(NSObject*)によってスタックポインタをバンプして、スタック上にobj2という名前の領域を確保します。

また、その行で実行する式がないため、デバッガでステップインすることはできません。あなたは限り実行が懸念しているように書いた元のコードを効果的に同一である

[[[NSObject alloc] init] release]; 

:注目すべき


は、あなたにコードを書き換えることができます。オプティマイザがなければ、スタックに何も格納されないという点で少し異なります。オプティマイザでは、元のコードと同じコードを生成する可能性があります。オプティマイザは、ローカル変数が不要なときにローカル変数を取り除くことができます(これは部分的に、最適化されたコードのデバッグが非常に難しい理由です)。


を考えると、この:

(11) void f() 
(12) { 
(13) NSObject *obj1 = [[NSObject alloc] init]; // Line 1 
(14)  
(15) [obj1 release]; // Line 2 
(16)  
(17) NSObject *obj2; // Line 3 
(18)} 

これは、最適化されていないx86_64のアセンブリです。 "フィックスアップ"のものは無視してください。 callq行を見てください。上記のobjc_msgSend()の実際の呼び出しです。 x86_64では、%rdi - レジスタ - は、すべての関数呼び出しの引数0です。したがって、%rdiはメソッド呼び出しの対象が行く場所です。 %raxは戻り値に使用されるレジスタです。だから、

、あなたは「最初callqの戻り値を取り、次のcallqの最初の引数として渡すと言い、別のcallq続いmovq %rax, %rdi続いcallqを、参照してください。については

あなたの変数はcallqの後にmovq %rax, -8(%rbp)のようなものがあります。これは "callqによって返されたものを取り、それをスタックの現在の場所に書き出し、スタックポインタを8つの場所に移動します(スタックが小さくなる)"残念ながら、アセンブリには変数名が表示されません。

_f:          ## @f 
    .cfi_startproc 
Lfunc_begin0: 
    .loc 1 12 0     ## /tmp/asdfafsd/asdfafsd/main.m:12:0 
## BB#0: 
    pushq %rbp 
Ltmp2: 
    .cfi_def_cfa_offset 16 
Ltmp3: 
    .cfi_offset %rbp, -16 
    movq %rsp, %rbp 
Ltmp4: 
    .cfi_def_cfa_register %rbp 
    subq $32, %rsp 
    leaq l_objc_msgSend_fixup_release(%rip), %rax 
    leaq l_objc_msgSend_fixup_alloc(%rip), %rcx 
    .loc 1 13 0 prologue_end  ## /tmp/asdfafsd/asdfafsd/main.m:13:0 
Ltmp5: 
    movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rdx 
    movq %rdx, %rdi 
    movq %rcx, %rsi 
    movq %rax, -24(%rbp)   ## 8-byte Spill 
    callq *l_objc_msgSend_fixup_alloc(%rip) 
    movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi 
    movq %rax, %rdi 
    callq _objc_msgSend 
    movq %rax, -8(%rbp) 
    .loc 1 15 0     ## /tmp/asdfafsd/asdfafsd/main.m:15:0 
    movq -8(%rbp), %rax 
    movq %rax, %rdi 
    movq -24(%rbp), %rsi   ## 8-byte Reload 
    callq *l_objc_msgSend_fixup_release(%rip) 
    .loc 1 18 0     ## /tmp/asdfafsd/asdfafsd/main.m:18:0 
    addq $32, %rsp 
    popq %rbp 
    ret 
Ltmp6: 
Lfunc_end0: 
笑いのために

、(-Os - 最速、最小の、展開されたコードのデフォルト)をオンにオプティマイザで生成されたアセンブリを見て:

最初に注意する - と、この質問(3)に戻る - それはです。最初と最後の命令の外に%rbpの操作はありません。つまり、何もスタックにプッシュ/プルされません。文字通り、obj1obj2は、コンパイラが同等のコードを生成する必要がないため、これまでに宣言されたという証拠はありません。

すべてはレジスタを介して行われ、move %rax, %rdiが2つあることに注意してください。最初は「+allocの結果を取り、-initの呼び出しの最初の引数として使用」であり、第二は、-initの結果を取り、-releaseに引数としてそれを使用する」である以外に

; %rsiコールを機能させる第2引数はx86_64の上に存在する場所であるメソッド呼び出しについて - 。objc_msgSend()関数への呼び出しのために - その引数が呼び出されるメソッド(セレクタ)の名前を含む常にう

Lfunc_begin0: 
    .loc 1 12 0     ## /tmp/asdfafsd/asdfafsd/main.m:12:0 
## BB#0: 
    pushq %rbp 
Ltmp2: 
    .cfi_def_cfa_offset 16 
Ltmp3: 
    .cfi_offset %rbp, -16 
    movq %rsp, %rbp 
Ltmp4: 
    .cfi_def_cfa_register %rbp 
    .loc 1 13 0 prologue_end  ## /tmp/asdfafsd/asdfafsd/main.m:13:0 
Ltmp5: 
    movq L_OBJC_CLASSLIST_REFERENCES_$_(%rip), %rdi 
    leaq l_objc_msgSend_fixup_alloc(%rip), %rsi 
    callq *l_objc_msgSend_fixup_alloc(%rip) 
    movq L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi 
    movq %rax, %rdi 
    callq *[email protected](%rip) 
    .loc 1 15 0     ## /tmp/asdfafsd/asdfafsd/main.m:15:0 
    leaq l_objc_msgSend_fixup_release(%rip), %rsi 
    movq l_objc_msgSend_fixup_release(%rip), %rcx 
    movq %rax, %rdi 
    popq %rbp 
    jmpq *%rcx # TAILCALL 
Ltmp6: 
Lfunc_end0: 

さらに詳しく知りたい場合はメソッドの発送、私はbit of a guideを書いた。これはobjc_msgSend()のいくつかのバージョンですが、これまでとは異なります。

ARMコードは哲学的には同じように動作しますが、生成されたアセンブリは少し異なります。


あなたが生成されたアセンブリを見れば、私は

^^ 3行以上

にステップすることはできませんなぜ私はまだ理解できない、変数宣言のために生成されたものは何もありません。少なくとも直接的ではありません。一番近いのはで、initの結果を移動しますが、の呼び出し後にはになります。

NSObject *obj2;の場合、コンパイラはコードを生成しません。オプティマイザを無効にしても、

これは、変数宣言が式ではないためです。値を保持するために使用するためのラベル(開発者)を提供する以外には、実際には何もしません。生成されたコードがある変数を実際に使用するときだけです。

したがって、デバッガをステップ実行しているときは、何もしないので、その行をスキップします。

+0

"行に実行された式があるため、この行をデバッガでステップ実行することができます" これはわかりません!実行される式とは何ですか? – DungProton

+0

最後に説明が追加されました。 – bbum

+0

あなたは「何もすることがないので、その行をスキップする」と言った。だから、私が '{}'を書いただけで、デバッガはまだステップオーバーしました。私も '{}'は本当に何もしないと思う。 – DungProton

関連する問題