objective c - What did the compiler and run-time system really do in my generated assembly? -
i understand how generated assembly , runtime work together, , came across question while stepping through generated assembly code.
source example
here 3 lines of objective-c, running in xcode 4.5:
// line 1: nsobject *obj1 = [[nsobject alloc] init]; // line 2: [obj1 release]; // line 3: nsobject *obj2;
comparing generated assembly
stepping through generated assembly, made few observations.
before line 1, address of obj1
shown:
obj1 (nsobject*) 0x00003604
after line 1, changes:
obj1 nsobject * 0x08122110
observations
1) address of obj1
changed. when source code compiled, compiler allocates temporarily memory obj1
. then, (after line 1) compiler apparently re-allocates, object's address changes.
2) after line 2, address of obj2
still same (0x08122110
)! when call [obj1 release]
, telling compiler: "i don't need anymore. please take away." system doing release @ point in future , can not seem control directly.
3) debugger can't step on line 3. don't understand why won't!
question
in terms of creating , destroying objects, compiler doing these lines of code (specifically "alloc-init", release, , nsobject pointer declaration without assignment)? also, why won't debugger let me step on third line? can debugger not see it?
along answer, if can please recommend documents or book compiler , run-time system do, appreciate it. thank much!
marcus's answer quite good, here more details (i'd been meaning brush reading generated assembly; having try , explain best way).
nsobject *obj1 = [[nsobject alloc] init]; // line 1
the compiler compiles 2 function calls objc_msgsend()
. first calls +alloc
method on nsobject
class. result of function call becomes first argument -- target object -- of second function call calls method -init
.
the result of calling init
stored on stack in chunk of memory have declared being named obj1
has type of a pointer instance of nsobject.
you can step through line in debugger because there executed expression on line. if code written as:
nsobject *obj1; // declaration obj1 = [[nsobject alloc] init];
then find can't step through declaration.
before obj1 = [[nsobject alloc] init];, value of
obj1is *undefined* under manual retain release, **will automatically set to
nil` (0) under arc** (thereby eliminating source of bugs marcus indicated).
[obj1 release]; // line 2
this line invokes release
method on instance of nsobject pointed to obj1
.
nsobject *obj2; // line 3
this line nothing. if compiler's optimizer turned on, there no code generated @ all. without optimizer, compiler may bump stack pointer sizeof(nsobject*)
reserve space on stack name obj2
.
and, again, can't step through in debugger because there no expression execute on line.
of note, rewrite code as:
[[[nsobject alloc] init] release];
that identical original code wrote far execution concerned. without optimizer, little bit different in won't store on stack. optimizer, generate identical code original code. optimizer quite @ eliminating local variables when aren't needed (which partially why debugging optimized code hard).
given this:
(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)}
this unoptimized x86_64 assembly. ignore "fixup" stuff. @ callq
lines; actual calls objc_msgsend() described above. on x86_64, %rdi -- register -- argument 0 function calls. thus, %rdi target of method calls goes. %rax register used return values.
so, when see callq, followed movq %rax, %rdi
, followed callq, says "take return value of first callq
, pass first argument next callq
.
as variables, you'll see things movq %rax, -8(%rbp)
after callq
. says "take whatever returned callq
, write current spot on stack, move stack pointer down 8 locations (the stack grows down)". unfortunately, assembly doesn't show variable names.
_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:
for giggles, have @ assembly generated optimizer turned on (-os -- fastest, smallest, default deployed code):
the first thing note -- , gets question (3) -- there no manipulation of %rbp
outside of first , last instructions. is, nothing pushed onto or pulled off stack; quite literally, there no evidence obj1
, obj2
ever declared because compiler didn't need them generate equivalent code.
everything done via registers , you'll note there 2 move %rax, %rdi
. first "take result of +alloc
, use first argument call -init
" , second "take result of -init
, use argument -release
.
aside; %rsi
second argument function calls resides on x86_64. method calls -- calls objc_msgsend()
function -- argument contain name of method (the selector) called.
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 *_objc_msgsend@gotpcrel(%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:
if want learn more method dispatch, wrote bit of guide. couple of versions of objc_msgsend() out of date, still relevant.
note arm code works same way philosophically, generated assembly bit different , quite bit more of it.
i can't still understand why can't step on line 3 ^^
if @ generated assembly, there nothing generated variable declarations. @ least not directly. closest movq %rax, -8(%rbp)
moves result of init
, after 2 function calls.
for nsobject *obj2;
, compiler doesn't generate code. not optimizer disabled.
that because variable declaration not expression; doesn't other provide label -- developer -- use hold values. when use variable there code generated.
thus, when stepping in debugger, skips line because there nothing do.
Comments
Post a Comment