Objective c 编译器和运行时系统在生成的程序集中真正做了什么?
我想了解生成的程序集和运行时是如何协同工作的,并且在逐步完成一些生成的程序集代码时遇到了一个问题 源示例 下面是在XCode 4.5中运行的Objective-C的三行代码:Objective c 编译器和运行时系统在生成的程序集中真正做了什么?,objective-c,compiler-construction,runtime,Objective C,Compiler Construction,Runtime,我想了解生成的程序集和运行时是如何协同工作的,并且在逐步完成一些生成的程序集代码时遇到了一个问题 源示例 下面是在XCode 4.5中运行的Objective-C的三行代码: // Line 1: NSObject *obj1 = [[NSObject alloc] init]; // Line 2: [obj1 release]; // Line 3: NSObject *obj2; 比较生成的程序集 在生成的程序集中,我进行了一些观察 在第1行之前,obj1的地址如下所示: obj1
// Line 1:
NSObject *obj1 = [[NSObject alloc] init];
// Line 2:
[obj1 release];
// Line 3:
NSObject *obj2;
比较生成的程序集
在生成的程序集中,我进行了一些观察
在第1行之前,obj1的地址如下所示:
obj1 (NSObject*) 0x00003604
在第1行之后,它将更改:
obj1 NSObject * 0x08122110
观察结果
1) obj1
的地址已更改。编译源代码时,编译器会临时为obj1
分配内存。然后,(在第1行之后)编译器显然会重新分配,因此对象的地址会改变
2) 第2行之后,obj2
的地址仍然相同(0x08122110
)!当我调用[obj1 release]
时,我告诉编译器:“我不再需要这个了。请把它拿走。”但系统实际上是在将来某个时候发布的,我似乎无法直接控制它
3) 调试器无法跨过第3行。我不明白为什么它不会
问题
在创建和销毁对象方面,编译器实际如何处理这些代码行(特别是“alloc init”、一个发行版和一个没有赋值的NSObject指针声明)?还有,为什么调试器不让我跳过第三行?调试器不能看到它吗
除了回答,如果您能推荐一些关于编译器和运行时系统真正做什么的文档或书籍,我将不胜感激。多谢各位
obj1
的指针在堆栈上创建。它未初始化,这意味着它将包含该内存位置中的任何内容。由于使用未初始化的指针可能会导致未指定的行为,因此这是一个常见的错误源。一旦分配了对象,指针将用其地址初始化-release
消息发送到对象时,保留计数器通常减少1。如果retain计数器已经在一个位置,则调用-dealloc
方法,并将内存标记为空闲。只有指针指向的内存被标记为空闲,但指针保持不变。这就是为什么有些人喜欢在不再需要指针时将指针设置为nil
关于这本书的推荐书。我会推荐编译器:原理、技术和工具。Marcus的回答很好,但这里有一些更多的细节(我一直想重温阅读生成的汇编;必须实际尝试并解释这是最好的方法) 编译器编译对
objc\u msgSend()
的两个函数调用。第一个调用NSObject
类上的+alloc
方法。该函数调用的结果成为调用方法-init
的第二个函数调用的第一个参数——目标对象
然后,调用init
的结果存储在堆栈中的一块内存中,该内存块已声明为名为obj1
,该内存块具有指向NSObject实例的指针类型
您可以在调试器中单步执行这一行,因为这一行上有一个已执行的表达式。如果代码编写为:
NSObject *obj1; // declaration
obj1 = [[NSObject alloc] init];
然后你会发现你不能单步通过声明
在obj1=[[NSObject alloc]init];之前;,在手动保留释放下,
obj1的值为*未定义*,但在ARC**下**将自动设置为
nil`(0)(从而消除了所示的错误源)
此行调用由obj1
指向的NSObject实例上的release
方法
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,后面是movq%rax,%rdi
,后面是另一个callq,表示“获取第一个callq
的返回值,并将其作为第一个参数传递给下一个callq
至于变量,在callq
之后会看到类似movq%rax,-8(%rbp)
的内容。这表示“获取callq
返回的内容,将其写入堆栈上的当前位置,然后将堆栈指针向下移动8
[[[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)}
_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:
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: