今天我們來說一下關(guān)于孫源之前提出的那道經(jīng)典面試題.
題目如下:
@interface FJFPerson : NSObject
// name
@property (nonatomic, copy) NSString *name;
- (void)print;
@end
@implementation FJFPerson
- (void)print {
NSLog(@"my name is %@", self.name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [FJFPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
打印出來的結(jié)果為:
2019-10-02 19:13:52.387769+0800 FJFRuntimeInterviewQuestionDemo[16143:509566] my name is <ViewController: 0x7ff0aac05df0>
對于這個打印結(jié)果,我們先來說一下,之前比較官方的解釋,然后我們再來說一下對于這個解釋的疑問。
關(guān)于這個解釋這篇文章也解釋很清楚:
不同的是片仿,我是通過匯編來解釋堆棧關(guān)系,而我這邊的重點是在第二部分
尤辱,如果對于這個解釋已經(jīng)了解砂豌,可以直接看第二部分
。
一.結(jié)果的官方解釋
A.為什么不會崩潰
id cls = [FJFPerson class];
這句代碼里面的cls
指向的是FJFPerson
這個類光督。void *obj = &cls;
然后在這里obj
是一個指向cls
的指針阳距。而通過如下源碼:
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
}
我們可以看出這里objc_object
這個對象的首字段是isa
指向一個Class
。
也就是說结借,這里的obj
就相當(dāng)于一個FJFPerson
實例對象的指針筐摘,指向了cls
,而cls
就相當(dāng)于isa
指針,指向了FJFPerson
類船老。
如下圖所示:
-
[(__bridge id)obj print];
所以這里調(diào)用就相當(dāng)于FJFPerson
實例對象的調(diào)用[person print]
咖熟,是能夠正常調(diào)用
B.為什么打印出ViewController
對象
- 我們在
FJFPerson
的print
添加self
和&_name
的地址信息
- 我們在
@implementation FJFPerson
- (void)print {
NSLog(@"self: %p", self);
NSLog(@"self.name: %p", &_name);
NSLog(@"my name is %@", self.name);
}
@end
打印結(jié)果如下:
從這個地址信息,我們可以看出self
和name
之間的地址差8
個字節(jié)柳畔,即1個指針
的距離.
- 而在
id cls = [Spark class];
前面添加代碼NSString *str = @"11111"; NSLog(@"cls address:%p str address:%p",&cls,&str);
, 打印出如下信息:
可以看出:
-
cls
的地址比str
的地址值大8
個字節(jié)馍管。 -
self.name
和str
的地址值一樣,指向字符串11111
因為函數(shù)調(diào)用采用棧的形式薪韩,棧的地址是從高地址到低地址确沸,所以先入棧的str
比cls
大8
個字節(jié),而print
函數(shù)里面的self
地址和cls
地址一致俘陷,是因為[obj print]
的是通過cls
即isa
來進行方法調(diào)用罗捎,所以self
就是obj
,而self.name
的地址由于大self
地址8
個字節(jié),所以self.name
的地址剛好和str
地址一致拉盾。
也就是說這里棧參數(shù)的數(shù)據(jù)結(jié)構(gòu)格式桨菜,對應(yīng)了obj對象地址的數(shù)據(jù)結(jié)構(gòu)。
2. 至于為什么打印的是ViewController
對象:
從上面分析我們可以看出self.name
的值是在cls
之前入棧的值捉偏,與cls
相差8
個字節(jié)雷激,因此我們通過匯編分析下堆棧信息:
FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
0x10f8b10d0 <+0>: pushq %rbp
0x10f8b10d1 <+1>: movq %rsp, %rbp
0x10f8b10d4 <+4>: subq $0x40, %rsp - rsp - 0x40 ->開辟64個字節(jié)椞媸撸空間
0x10f8b10d8 <+8>: movq %rdi, -0x8(%rbp) - <ViewController: 0x7fd291512f90> 將self的值 給(rdp - 0x8)
0x10f8b10dc <+12>: movq %rsi, -0x10(%rbp) - rsi的 “viewDidLoad”告私, 將self的值給rdp - 0x10
0x10f8b10e0 <+16>: movq -0x8(%rbp), %rsi - 將self的值給rsi
0x10f8b10e4 <+20>: movq %rsi, -0x20(%rbp) - 將rsi的值給self給 內(nèi)存地址(rdp-0x20)
0x10f8b10e8 <+24>: movq 0x2e39(%rip), %rsi ; (void *)0x000000010f8b3f40: ViewController 將”ViewController”字符串地址給rsi
0x10f8b10ef <+31>: movq %rsi, -0x18(%rbp) - 將”ViewController”字符串的地址給(rbp - 0x18)
0x10f8b10f3 <+35>: movq 0x2d7e(%rip), %rsi ; “viewDidLoad" 將viewDidLoad的字符串地址給rsi
0x10f8b10fa <+42>: leaq -0x20(%rbp), %rdi - 將內(nèi)存地址(rbp-0x20)的地址給rdi
0x10f8b10fe <+46>: callq 0x10f8b184e ; symbol stub for: objc_msgSendSuper2 跳轉(zhuǎn)到objc_msgSendSuper2命令
0x10f8b1103 <+51>: movq 0x2de6(%rip), %rsi ; (void *)0x000000010f8b4008: FJFPerson 將文本地址給rsi
0x10f8b110a <+58>: movq 0x2d6f(%rip), %rdi ; “class” 將"class”給rdi
0x10f8b1111 <+65>: movq %rdi, -0x38(%rbp) - 將rdi(“class”)的值給(rbp-0x38)
0x10f8b1115 <+69>: movq %rsi, %rdi - 將rsi(“FJFPerson”)給rdi
0x10f8b1118 <+72>: movq -0x38(%rbp), %rsi - 將(rbp-0x38)的值給rsi
0x10f8b111c <+76>: callq *0x1ee6(%rip) ; (void *)0x00007fff503b1780: objc_msgSend 調(diào)用objc_msgSend方法
0x10f8b1122 <+82>: movq %rax, %rdi - 將返回值給rdi
0x10f8b1125 <+85>: callq 0x10f8b185a ; symbol stub for: objc_retainAutoreleasedReturnValue
0x10f8b112a <+90>: movq %rax, -0x28(%rbp) - 將cls返回值給(rbp-0x28)
-> 0x10f8b112e <+94>: leaq -0x28(%rbp), %rax 將rbp-0x28地址給rax
0x10f8b1132 <+98>: movq %rax, -0x30(%rbp) 將rax的值(obj)給(rbp-0x30)
0x10f8b1136 <+102>: movq -0x30(%rbp), %rdi 將(rbp-0x30)的值給rdi
0x10f8b113a <+106>: movq 0x2d47(%rip), %rsi ; “print” 將print給rsi
0x10f8b1141 <+113>: callq *0x1ec1(%rip) ; (void *)0x00007fff503b1780: objc_msgSend 進行print函數(shù)調(diào)用
經(jīng)過匯編的分析屎暇,我們很容易看出cls
的之前入棧的是self
即viewController
本身。
以上就是官方給出的分析驻粟,我們總結(jié)一下:
所有NSObject
對象的首地址
都是指向這個對象的所屬類根悼,反過來說如果一個地址指向某個類,我們可以把這個地址
當(dāng)做對象
去用蜀撑。所以編譯可以通過挤巡,進行方法調(diào)用也不會報錯。
打印結(jié)果是ViewController
對象的原因是因為cls
在棧上的數(shù)據(jù)結(jié)構(gòu)符合它作為真實類的數(shù)據(jù)結(jié)構(gòu)酷麦,self.name
的地址正好是self
對象的地址.
二. 存在的疑問
大家都知道:
在
arm64
架構(gòu)之前矿卑,isa
就是一個普通的指針,存儲著class
沃饶、Meta-Class
對象的內(nèi)存地址母廷。
從arm64
架構(gòu)開始,對isa
進行了優(yōu)化糊肤,變成了一個共用體(union)
結(jié)構(gòu)琴昆,還使用位域來存儲更多的信息。
也就是說在arm64
架構(gòu)中馆揉,一個實例對象比如person
的isa
指針并沒有直接指向FJFPerson
類业舍,而是需要isa
地址和相應(yīng)架構(gòu)的ISA_MASK
掩碼進行相與,才能得到真正的指向FJFPerson
類的地址升酣。
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
接下來我們再來分析下調(diào)用的方法:
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [FJFPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
-
id cls = [FJFPerson class];
這句代碼里cls
是指向FJFPerson
類的指針 -
void *obj = &cls;
這里的obj
是一個二級指針舷暮,是指向cls
的的指針,也就是說obj
里面存儲的就是一個單純的cls
地址值,并非是個共用體 -
[(__bridge id)obj print];
這里是進行一個消息的發(fā)送
我們將ViewController.m
轉(zhuǎn)換為C++
代碼看一下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp
我們可以看到如下C++
代碼:
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
id cls = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FJFPerson"), sel_registerName("class"));
void *obj = &cls;
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("print"));
}
我們可以看出這里是直接對obj
進行print
的消息發(fā)送噩茄。
接下來我們看下objc_msgSend
在arm64
架構(gòu)上的源碼:
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd, ...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in x17
* x16 reserved for our use but not used
*
********************************************************************/
.data
.align 3
.globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
.fill 16, 8, 0
.globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
.fill 256, 8, 0
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
cmp x0, #0 // nil check and tagged pointer check 檢查x0即isa是否為nil或者tagged Pointer
b.le LNilOrTagged // (MSB tagged pointer looks negative) 如果為nil或者tagged Pointer 就跳轉(zhuǎn)到 LNilOrTagged
ldr x13, [x0] // x13 = isa 將isa的值給x13
and x16, x13, #ISA_MASK // x16 = class 將isa與isa_mask進行與操作得到相關(guān)的類地址
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached 進入正常的緩存方法查找
LNilOrTagged:
b.eq LReturnZero // nil check nil的檢測
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
通過源碼我們可以分析:
[(__bridge id)obj print];
這句代碼走到源碼里面,源碼取obj
存儲的地址下面,也就是cls
的地址跟ISA_MASK
進行相與,我們會發(fā)現(xiàn)巢墅,cls
的地址與ISA_MASK
相與后的結(jié)果是cls
地址诸狭,我們加入一個FJFPerson
的實例對象tmpPerson
,發(fā)現(xiàn)tmpPerson
的isa
和ISA_MASK
相與也是真正的類FJFPerson
地址,即cls
的地址君纫。
(lldb) p/x cls
(id) $0 = 0x0000000104c98fe8
(lldb) p/x obj
(FJFPerson *) $1 = 0x000000016b16bd78
(lldb) p/x tmpPerson->isa
(Class) $2 = 0x000001a104c98fed FJFPerson
(lldb) p/x 0x000001a104c98fed & 0x0000000ffffffff8ULL
(unsigned long long) $3 = 0x0000000104c98fe8
(lldb) p/x 0x0000000104c98fe8 & 0x0000000ffffffff8ULL
(unsigned long long) $4 = 0x0000000104c98fe8
我們將FJFPerson
類地址與ISA_MASK
掩碼進行位數(shù)的比對:
0x0000000 1 0 2 f 1 4 f e 8
0x0000000 f f f f f f f f 8
我們會發(fā)現(xiàn)ISA_MASK
掩碼為1的位數(shù)是大于類地址的位數(shù)驯遇,而后面的8
即二進制的1000
是因為字節(jié)對齊而補上的000
,同時我們將其他幾個類地址打印輸出看一下:
這里我們可以推導(dǎo)出,類地址
是實例對象的isa指針
和ISA_MASK
掩碼相與后得到的地址蓄髓,所以當(dāng)類地址
再次和ISA_MASK
掩碼相與之后得到的肯定是類地址本身
叉庐。
因此,這道題目之所以能夠正常的調(diào)用会喝,就在于類地址的取值
的設(shè)計陡叠,而上面說的如果某個地址
指向類對象的地址
玩郊,我們可以把這個地址
當(dāng)做對象
去用的說法,是有點牽強的枉阵,至少在arm64
架構(gòu)上是這樣译红。
三.補充
有簡友說這道題目的打印是個巧合,并建議我看一下release
的相關(guān)匯編的堆棧兴溜。
這里我分別打印了 真機 iOS 13侦厚、iOS 12.2 、iOS9 release版本的匯編
,模擬器 iPhone8 iOS13 release版本的匯編
拙徽,模擬器 iPhone8 iOS11.2版本的匯編
.
分析如下:
1. 真機 iOS 13 刨沦、 iOS 13、iOS 12.2 膘怕、iOS9 release版本的匯編(正常輸出)
匯編代碼:
FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
0x102b165e0 <+0>: sub sp, sp, #0x30 ; =0x30 將sp-0x30想诅,開辟棧空間
0x102b165e4 <+4>: stp x29, x30, [sp, #0x20] 將x29,x30放在(x29棧幀地址岛心、x30程序鏈接寄存器)(sp+0x20)的地址處
0x102b165e8 <+8>: add x29, sp, #0x20 ; =0x20 將sp+0x20棧地址賦值給x29
-> 0x102b165ec <+12>: nop 空指令
0x102b165f0 <+16>: ldr x8, #0x28e0 ; (void *)0x0000000102b18ee0: ViewController 將0x28e0的地址給x8
0x102b165f4 <+20>: stp x0, x8, [sp, #0x10] 將x0, x8放入(sp+0x10)的地址處来破,x0是self(當(dāng)前viewController地址),x8是字符串(“ ViewController")
0x102b165f8 <+24>: nop 空指令
0x102b165fc <+28>: ldr x1, #0x2824 ; "viewDidLoad” 將0x2824的值賦值給x1
0x102b16600 <+32>: add x0, sp, #0x10 ; =0x10 將sp+0x10的值賦值給x0
0x102b16604 <+36>: bl 0x102b16990 ; symbol stub for: objc_msgSendSuper2 調(diào)用objc_msgSendSuper2
0x102b16608 <+40>: nop 空指令
0x102b1660c <+44>: ldr x0, #0x288c ; (void *)0x0000000102b18fa8: FJFPerson將0x288c的值賦值給ldr
0x102b16610 <+48>: nop 空指令
0x102b16614 <+52>: ldr x1, #0x2814 ; "class” 將0x2814的值賦值給x1
0x102b16618 <+56>: bl 0x102b16984 ; symbol stub for: objc_msgSend 調(diào)用objc_msgSend
0x102b1661c <+60>: mov x29, x29 將x29的值賦值給x29鹉梨,恢復(fù)棧
0x102b16620 <+64>: bl 0x102b169a8 ; symbol stub for: objc_retainAutoreleasedReturnValue 調(diào)用autorelase
0x102b16624 <+68>: str x0, [sp, #0x8] 將x0的值存儲到sp+0x8的位置 (void *)0x0000000102e80f80: FJFPerson即為cls
0x102b16628 <+72>: nop 空指令
0x102b1662c <+76>: ldr x1, #0x2804 ; "print” 調(diào)用print
0x102b16630 <+80>: add x0, sp, #0x8 ; =0x8
0x102b16634 <+84>: bl 0x102b16984 ; symbol stub for: objc_msgSend
0x102b16638 <+88>: ldr x0, [sp, #0x8]
0x102b1663c <+92>: bl 0x102b1699c ; symbol stub for: objc_release
0x102b16640 <+96>: ldp x29, x30, [sp, #0x20]
0x102b16644 <+100>: add sp, sp, #0x30 ; =0x30
0x102b16648 <+104>: ret
堆棧信息:
寄存器信息:
2. 模擬器 iPhone8 iOS13 release版本的匯編(正常輸出)
FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
0x10dfde4cc <+0>: pushq %rbp rdp入棧
0x10dfde4cd <+1>: movq %rsp, %rbp 將rsp的值賦值給rdp
0x10dfde4d0 <+4>: pushq %r14 r14入棧
0x10dfde4d2 <+6>: pushq %rbx rbx入棧 rbx為self的地址值
0x10dfde4d3 <+7>: subq $0x20, %rsp rsp - 0x20的值給rsp
0x10dfde4d7 <+11>: leaq -0x28(%rbp), %rax 將rdp-0x28的地址給rax
0x10dfde4db <+15>: movq %rdi, (%rax) 將rdi的值賦值給rax所在地址空間 rdi是self的地址值
0x10dfde4de <+18>: movq 0x29fb(%rip), %rcx ; (void *)0x000000010dfe0ef8: ViewController 將字符串(viewController)賦值給rcx
0x10dfde4e5 <+25>: movq %rcx, 0x8(%rax) 將rcx的值賦值給(rax+0x8),也就是字符串”viewController”賦值給rax+0x8所在地址
0x10dfde4e9 <+29>: movq 0x2940(%rip), %rsi ; “viewDidLoad”讳癌,將字符串”viewDidLoad”的值賦值給rsi
0x10dfde4f0 <+36>: movq %rax, %rdi 將rax的值賦值給rdi, rdi的值為rdp-0x28
0x10dfde4f3 <+39>: callq 0x10dfde85e ; symbol stub for: objc_msgSendSuper2 調(diào)用objc_msgSendSuper2
-> 0x10dfde4f8 <+44>: movq 0x29a9(%rip), %rdi ; (void *)0x000000010dfe0fc0: FJFPerson
0x10dfde4ff <+51>: movq 0x2932(%rip), %rsi ; "class"
0x10dfde506 <+58>: movq 0x1afb(%rip), %r14 ; (void *)0x00007fff503b1780: objc_msgSend
0x10dfde50d <+65>: callq *%r14 調(diào)用objc_msgSend
0x10dfde510 <+68>: movq %rax, %rdi 將rax的值賦值給rdi,rax即為cls
0x10dfde513 <+71>: callq 0x10dfde86a ; symbol stub for: objc_retainAutoreleasedReturnValue 調(diào)用autorelease
0x10dfde518 <+76>: leaq -0x18(%rbp), %rbx 將(rbp-0x18)的地址值給rbx
0x10dfde51c <+80>: movq %rax, (%rbx) 將rax的值放入rbx所在的地址值,cls放在(rbp-0x18)
0x10dfde51f <+83>: movq 0x291a(%rip), %rsi ; "print" 將”print“地址給rsi
0x10dfde526 <+90>: movq %rbx, %rdi 將rbx的地址給rdi,即rdi的地址為(rbp-0x18)
0x10dfde529 <+93>: callq *%r14
0x10dfde52c <+96>: movq (%rbx), %rdi
0x10dfde52f <+99>: callq *0x1adb(%rip) ; (void *)0x00007fff503cb040: objc_release
0x10dfde535 <+105>: addq $0x20, %rsp
0x10dfde539 <+109>: popq %rbx
0x10dfde53a <+110>: popq %r14
0x10dfde53c <+112>: popq %rbp
0x10dfde53d <+113>: retq
堆棧信息:
寄存器信息:
3. 模擬器 iPhone8 iOS11.2版本的匯編 (崩潰)
匯編代碼
FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
0x104ce74cc <+0>: pushq %rbp rdp入棧
0x104ce74cd <+1>: movq %rsp, %rbp 將rsp的值賦值給rdp
0x104ce74d0 <+4>: pushq %r14 r14入棧 _UIApplicationLinkedOnVersion
0x104ce74d2 <+6>: pushq %rbx rbx入棧 "count" 是char類型的字符串
0x104ce74d3 <+7>: subq $0x20, %rsp rsp - 0x20的值給rsp
0x104ce74d7 <+11>: leaq -0x28(%rbp), %rax 將rdp-0x28的地址給rax
0x104ce74db <+15>: movq %rdi, (%rax) 將rdi的值賦值給rax所在地址空間
0x104ce74de <+18>: movq 0x29fb(%rip), %rcx ; (void *)0x0000000104ce9ef8: ViewController 將字符串(viewController)賦值給rcx
0x104ce74e5 <+25>: movq %rcx, 0x8(%rax) 將rcx的值賦值給rax+0x8地址
0x104ce74e9 <+29>: movq 0x2940(%rip), %rsi ; “viewDidLoad" 將字符串”viewDidLoad”的值賦值給rsi
0x104ce74f0 <+36>: movq %rax, %rdi 將rax的值賦值給rdi
0x104ce74f3 <+39>: callq 0x104ce785e ; symbol stub for: objc_msgSendSuper2 調(diào)用objc_msgSendSuper2
-> 0x104ce74f8 <+44>: movq 0x29a9(%rip), %rdi ; (void *)0x0000000104ce9fc0: FJFPerson 將FJFPerson地址給rdi
0x104ce74ff <+51>: movq 0x2932(%rip), %rsi ; “class" 將字符串”class“的地址值給rsi
0x104ce7506 <+58>: movq 0x1afb(%rip), %r14 ; (void *)0x000000010560b940: objc_msgSend 將objc_msgSend給r14
0x104ce750d <+65>: callq *%r14 調(diào)用objc_msgSend
0x104ce7510 <+68>: movq %rax, %rdi 將rax給rdi存皂,rax為cls
0x104ce7513 <+71>: callq 0x104ce786a ; symbol stub for: objc_retainAutoreleasedReturnValue 調(diào)用objc_retainAutoreleasedReturnValue
0x104ce7518 <+76>: leaq -0x18(%rbp), %rbx 將(rbp-0x18)的值給rbx,
0x104ce751c <+80>: movq %rax, (%rbx) 將rax的值放入rbx指向的地址空間
0x104ce751f <+83>: movq 0x291a(%rip), %rsi ; “print” 將”print“地址給rsi
0x104ce7526 <+90>: movq %rbx, %rdi 將rbx的值給rdi
0x104ce7529 <+93>: callq *%r14 調(diào)用objc_msgSend
0x104ce752c <+96>: movq (%rbx), %rdi
0x104ce752f <+99>: callq *0x1adb(%rip) ; (void *)0x0000000105608cc0: objc_release
0x104ce7535 <+105>: addq $0x20, %rsp
0x104ce7539 <+109>: popq %rbx
0x104ce753a <+110>: popq %r14
0x104ce753c <+112>: popq %rbp
0x104ce753d <+113>: retq
堆棧日志:
寄存器信息:
從以上測試結(jié)果晌坤,我們可以看出,是否崩潰取決于在cls
之前入棧的值是否是NSObject
類型旦袋,能否打印出my name is <ViewController: 0x7ff0aac05df0>
,根本在于cls
之前入棧的是否是self
的地址骤菠。
因此之前的打印結(jié)果是ViewController對象的原因:
因為cls
在棧上的數(shù)據(jù)結(jié)構(gòu)符合它作為真實類的數(shù)據(jù)結(jié)構(gòu),self.name
的地址正好是self
對象的地址,這個說法并沒有錯誤疤孕。
最后
如果大家有什么疑問或者意見向左的地方商乎,歡迎大家留言討論。