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 ofobj1is *undefined* under manual retain release, **will automatically set tonil` (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