Odpowiedź Marcusa jest całkiem dobra, ale tutaj jest trochę więcej szczegółów (chciałbym odświeżyć odczyt wygenerowanego zespołu, a właściwie próbować i wyjaśnić, to jest najlepszy sposób).
NSObject *obj1 = [[NSObject alloc] init]; // Line 1
kompilator kompiluje dwa wywołania funkcji objc_msgSend()
. Pierwszy wywołuje metodę +alloc
na klasie NSObject
. Wynikiem tego wywołania funkcji staje się pierwszy argument - obiekt docelowy - drugiego wywołania funkcji, który wywołuje metodę -init
.
Wynikiem wywoływania init
są następnie przechowywane na stosie w fragmencie pamięci, która została zadeklarowana jako nazwany obj1
która ma typ wskaźnik do instancji NSObject.
Możesz przejść przez tę linię w debuggerze , ponieważ jest wykonywane wyrażenie na linii. Jeśli kod został napisany jako:
NSObject *obj1; // declaration
obj1 = [[NSObject alloc] init];
Wtedy okazałoby się, że nie można przekroczyć deklaracji.
Przed obj1 = [[NSObject alloc] init];, the value of
obj1 is *undefined* under Manual Retain Release, but **will be automatically set to
nil` (0) pod ARC ** (eliminując w ten sposób źródło błędów wskazanych przez Marcusa).
[obj1 release]; // Line 2
Linia ta wywołuje metodę release
na wystąpienie NSObject wskazał przez obj1
.
NSObject *obj2; // Line 3
Ta linia skutecznie nic nie robi. Jeśli optymalizator kompilatora został włączony, w ogóle nie byłoby generowanego kodu. Bez optymalizatora kompilator może podnieść wskaźnik stosu o sizeof(NSObject*)
, aby zarezerwować miejsce na stosie o nazwie obj2
.
I znowu nie można tego zrobić w debugerze, ponieważ nie ma żadnego wyrażenia do wykonania w tym wierszu.
Warto zauważyć, że można przepisać kod jako:
[[[NSObject alloc] init] release];
które byłyby skutecznie identyczny z oryginalnym kodem napisałeś o ile egzekucja dotyczy. Bez optymalizatora będzie trochę inaczej, ponieważ nie będzie niczego przechowywać na stosie. Za pomocą optymalizatora prawdopodobnie wygeneruje identyczny kod jak w oryginalnym kodzie.Optymalizator całkiem dobrze eliminuje zmienne lokalne, gdy nie są one potrzebne (co również częściowo tłumaczy, dlaczego debugowanie zoptymalizowanego kodu jest tak trudne).
Biorąc pod uwagę to:
(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)}
To unoptimized montaż x86_64. Zignoruj "poprawkę". Spójrz na linie callq
; są to rzeczywiste wywołania objc_msgSend(), jak opisano powyżej. Na x86_64% rdi - register - jest argumentem 0 dla wszystkich wywołań funkcji. Tak więc% rdi jest tam, gdzie cel wywołań metod idzie. % rax jest rejestrem używanym do zwracania wartości.
Tak, gdy pojawi się callq, a następnie movq %rax, %rdi
, a następnie innym callq, który mówi „weź wartość zwracaną pierwszego callq
i przekazać go jako pierwszy argument do następnej callq
.
Co do twoje zmienne, po callq
zobaczysz takie rzeczy, jak movq %rax, -8(%rbp)
. Mówi "weź wszystko, co zostało zwrócone przez callq
, zapisz je do bieżącego miejsca na stosie, a następnie przesuń wskaźnik stosu w dół o 8 lokalizacji (stos rośnie") Niestety, w zestawie nie są wyświetlane nazwy zmiennych:
_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:
Dla chichotów, zajrzyj na zgromadzeniu generowanego z optymalizatora włączony (-os - najszybsze, najmniejsze, domyślny dla kodu wdrożonej):
Pierwszą rzeczą, aby pamiętać - i to powraca do pytania (3) - czy to nie ma manipulacji %rbp
poza pierwszą i ostatnią instrukcją. Oznacza to, że nic nie jest popychane lub wyciągane ze stosu; dosłownie, nie ma dowodów, że kiedykolwiek zostały zadeklarowane, ponieważ kompilator nie potrzebował ich do wygenerowania równoważnego kodu.
Wszystko odbywa się za pośrednictwem rejestrów, a należy pamiętać, że istnieją dwie wartości: move %rax, %rdi
. Pierwszym z nich jest „wziąć wynik +alloc
i używać go jako pierwszy argument na wezwanie do -init
”, a drugi „take wynik -init
i używać go jako argumentu do -release
Poza;. %rsi
gdzie drugi argument do funkcjonowania połączeń znajduje się na x86_64 dla wywołań metod. - w odniesieniu do wywołań funkcji objc_msgSend()
- argument ten będzie zawsze zawierać nazwę metody (wybierak), aby nazwać
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:
.
Jeśli chcesz dowiedzieć się więcej o metoda wysyłki, napisałem bit of a guide. Jest to kilka wersji objc_msgSend() nieaktualnych, ale nadal istotnych.
Zauważ, że kod ARM działa w taki sam sposób filozoficznie, ale wygenerowany zespół będzie nieco inny i trochę go więcej.
nie mogę jeszcze zrozumieć, dlaczego nie mogę przechodzić nad linią 3 ^^
Jeśli spojrzeć na wygenerowanym montażu, nie ma nic generowane dla zmiennej deklaracji. Przynajmniej nie bezpośrednio. Najbliższy będzie movq %rax, -8(%rbp)
, który przenosi wynik init
na, ale to jest po dwóch wywołaniach funkcji.
Dla NSObject *obj2;
kompilator nie generuje żadnego kodu. Nawet przy wyłączonym optymalizatorze.
Dzieje się tak dlatego, że deklaracja zmiennej nie jest wyrażeniem; w rzeczywistości nie robi nic poza dostarczeniem etykiety - deweloperowi - do przechowywania wartości. Dopiero wtedy, gdy faktycznie używasz zmiennej, która generuje kod.
Tak więc, gdy wkracza się do debuggera, pomija tę linię, ponieważ nie ma nic do zrobienia.
Nie wiem, dlaczego został zamknięty. "Dlaczego debugger nie pozwoli mi przekroczyć tej linii" jest sam w sobie punktem zwrotnym dla wielu nowych programistów. Zrozumienie "ekspresji, która coś robi", w przeciwieństwie do "tej linii jest przeznaczona wyłącznie dla wygody [programisty] i nie robi zbyt wiele" jest całkiem przydatne. I ogólne "co do cholery robi kompilator z tym i jak wchodzi w interakcję z runtime" jest również dość interesujące, choć szczegóły zazwyczaj nie są potrzebne przez większość twórców przez większość czasu. – bbum
Nie mogę jeszcze zrozumieć, dlaczego nie mogę przekroczyć linii 3 ^^ – DungProton
Ponieważ wiersz 3 nie jest instrukcją ("zrób to"), jest to deklaracja ("istnieje"). Przechodzenie między instrukcjami. Deklaracja z inicjatorem, podobnie jak linia 1, jest wystarczająco blisko, aby wykonać krok, ponieważ inicjator jest czymś do zrobienia - co najmniej wstawia coś do zmiennej, a twój inicjator w linii 1 również tworzy obiekt, który stawia tam. –