objc反汇编分析__strong和__weak

如题所说反汇编看__strong和__weak的真实样子,代码列举自然多,篇幅长不利于阅读,我就先搬出结论,后面是分析。

在NON-ARC环境,__strong和__weak不起作用。相反在ARC环境中会自动生成许多代码,__strong和__weak将生成指针引用这样的C++类,它们会在离开生命周期作用的范围(可以推广到作为实例的成员的情况)之前析构。而ARC中指针默认带__strong属性,也就是__strong完成了auto reference count。它们的伪代码如下:

template<typename _Tptr>
struct __weak
{
    _Tptr p;
    ~__weak()
    {
        destoryWeak(&p);
    }
    __weak()
    {
        p = initWeak(&p, nil);
    }
    __weak(id newObj)
    {
        p = initWeak(&p, newObj);
    }
    __weak(__strong<_Tptr>& other)
    {
        p = storeWeak(&p, *other);
    }
    void operator= (__strong<_Tptr>& other)
    {
        p = storeWeak(&p, *other);
    }
    void operator= (__weak<_Tptr>& other)
    {
        id newObj = *other;
        p = storeWeak(&p, newObj);
        [newObj release];
    }
    id operator*()
    {
        return loadWeakRetained(&p);
    }
};

template<typename _Tptr>
struct __strong
{
    _Tptr p;
    ~__strong()
    {
        storeStrong(&p, nil);
    }
    __strong()
    {
        p = nil;
    }
    __strong(id newObj)
    {
        p = [newObj retain];
    }
    __strong(__strong<_Tptr>& other)
    {
        storeStrong(&p, *other);
    }
    void operator= (__strong<_Tptr>& other)
    {
        storeStrong(&p, *other);
    }
    void operator= (__weak<_Tptr>& other)
    {
        id newObj = *other;
        storeStrong(&p, newObj);
        [newObj release];
    }
    id operator* ()
    {
        return (id)p;
    }
};

template<typename _Tptr>
id msgSend(__strong<_Tptr>& strong, SEL sel, ...)
{
    return msgSend(*strong, sel, ...);
}

template<typename _Tptr>
id msgSend(__weak<_Tptr>& weak, SEL sel, ...)
{
    id receiver = *weak;
    id res = msgSend(*weak, sel, ...);
    [receiver release];
    return res;
}

void storeStrong(id* loc, id newObj)
{
    [*loc release];
    *loc = [newObj retain];
}

 

下面就是分析,代码和汇编代码列举,对比。

我们先请出我们的嘉宾,还是a,b,c。

a 是全局大爷,无视HP值,与程序一样长寿。

b 是tagged幽灵,它真切地像对象一样反应,而当你想真实看清它指向的那块地却找不到影,但它又像真的存在。

c 是临记,在堆中芸芸众生之一,在戏份过后,就会因为HP消耗而领便当。

我们为它们分别安排带__strong的sa,sb,sc,以及__weak的wa,wb,wc。

它们之间通过相互赋值,赋不同的值,抑或它们调用引用,它们倾情上演__strong&__weak,not Tom & Jerry,敬请大家期待,现在隆重开场。

 

先来看定义,想一想整个运作过程:   

-(void)testNSString2
{
    NSString* a = @"abc";
    NSString* b = [NSString stringWithUTF8String:"abc"];
    NSString* c = [@"ab" stringByAppendingString:@"c"];
    __strong id sa = a;
    __strong id sb = b;
    __strong id sc = c;
    __weak id wa = a;
    __weak id wb = sb;
    __weak id wc = c;
    sc = wc;
    wc = sb;
    [wc isEqualToString:sc];
}

 

下面开始反汇编对比。在NON-ARC环境中,objc指针和普通c指针一样进行起无差别格斗,指针你来我往直接赋值指向,__strong和__weak不起作用。

    0x1060580d0 <+0>:   pushq  %rbp
    0x1060580d1 <+1>:   movq   %rsp, %rbp
    0x1060580d4 <+4>:   subq   $0x70, %rsp
    0x1060580d8 <+8>:   leaq   0x1d90(%rip), %rdx        ; "abc"
    0x1060580df <+15>:  leaq   0x1fda(%rip), %rax        ; @"abc"
    0x1060580e6 <+22>:  movq   %rdi, -0x8(%rbp)
    0x1060580ea <+26>:  movq   %rsi, -0x10(%rbp)
->  0x1060580ee <+30>:  movq   %rax, -0x18(%rbp)
    0x1060580f2 <+34>:  movq   0x2e77(%rip), %rax        ; (void *)0x000000010636eb20: NSString
    0x1060580f9 <+41>:  movq   0x2de8(%rip), %rsi        ; "stringWithUTF8String:"
    0x106058100 <+48>:  movq   %rax, %rdi
    0x106058103 <+51>:  callq  0x106058a08               ; symbol stub for: objc_msgSend
    0x106058108 <+56>:  leaq   0x1fd1(%rip), %rdx        ; @"ab"
    0x10605810f <+63>:  leaq   0x1fea(%rip), %rsi        ; @"'c'"
    0x106058116 <+70>:  movq   %rax, -0x20(%rbp)
    0x10605811a <+74>:  movq   0x2dcf(%rip), %rax        ; "stringByAppendingString:"
    0x106058121 <+81>:  movq   %rdx, %rdi
    0x106058124 <+84>:  movq   %rsi, -0x60(%rbp)
    0x106058128 <+88>:  movq   %rax, %rsi
    0x10605812b <+91>:  movq   -0x60(%rbp), %rdx
    0x10605812f <+95>:  callq  0x106058a08               ; symbol stub for: objc_msgSend
    0x106058134 <+100>: movq   %rax, -0x28(%rbp)
    0x106058138 <+104>: movq   -0x18(%rbp), %rax
    0x10605813c <+108>: movq   %rax, -0x30(%rbp)
    0x106058140 <+112>: movq   -0x20(%rbp), %rax
    0x106058144 <+116>: movq   %rax, -0x38(%rbp)
    0x106058148 <+120>: movq   -0x28(%rbp), %rax
    0x10605814c <+124>: movq   %rax, -0x40(%rbp)
    0x106058150 <+128>: movq   -0x18(%rbp), %rax
    0x106058154 <+132>: movq   %rax, -0x48(%rbp)
    0x106058158 <+136>: movq   -0x38(%rbp), %rax
    0x10605815c <+140>: movq   %rax, -0x50(%rbp)
    0x106058160 <+144>: movq   -0x28(%rbp), %rax
    0x106058164 <+148>: movq   %rax, -0x58(%rbp)
    0x106058168 <+152>: movq   -0x58(%rbp), %rax
    0x10605816c <+156>: movq   %rax, -0x40(%rbp)
    0x106058170 <+160>: movq   -0x38(%rbp), %rax
    0x106058174 <+164>: movq   %rax, -0x58(%rbp)
    0x106058178 <+168>: movq   -0x58(%rbp), %rdi
    0x10605817c <+172>: movq   -0x40(%rbp), %rax
    0x106058180 <+176>: movq   0x2d81(%rip), %rsi        ; "isEqualToString:"
    0x106058187 <+183>: movq   %rax, %rdx
    0x10605818a <+186>: callq  0x106058a08               ; symbol stub for: objc_msgSend
    0x10605818f <+191>: movb   %al, -0x61(%rbp)
    0x106058192 <+194>: addq   $0x70, %rsp
    0x106058196 <+198>: popq   %rbp
    0x106058197 <+199>: retq   

 

接着分析另一种环境。

在ARC环境中,战斗全面升级,各种华丽招式层出不穷,拳来脚往眼花獠乱。

先来看a,b,c。虽然它们没有表明它们是__strong部落,还是__weak联盟,但从它们扭着大pp的步姿,就知道它们骨子里透着__strong的味道。初始值时都加了一招objc_retain。还有一招objc_retainAutoreleaseReturnValue,就如其名对autorelease过的返回值retain。

恭喜你发现了__strong<NSString*> a, b, c!!!

NSString* a = @"abc";

NSString* b = [NSString stringWithUTF8String:"abc"];

NSString* c = [@"ab" stringByAppendingString:@"c"];

->  0x103658933 <+19>:  leaq   0x27e6(%rip), %rdi        ; @"abc"
    0x10365893a <+26>:  movq   0x26f7(%rip), %rsi        ; (void *)0x0000000103b6cd00: objc_retain
    0x103658941 <+33>:  movq   %rsi, -0x80(%rbp)
    0x103658945 <+37>:  callq  *%rsi
    0x103658947 <+39>:  movq   %rax, -0x18(%rbp)         ; // NSString* a = @"abc";
    0x10365894b <+43>:  movq   0x3656(%rip), %rdi        ; (void *)0x0000000103970b20: NSString
    0x103658952 <+50>:  movq   0x35c7(%rip), %rsi        ; "stringWithUTF8String:"
    0x103658959 <+57>:  leaq   0x2507(%rip), %rdx        ; "abc"
    0x103658960 <+64>:  movq   0x26c1(%rip), %rax        ; (void *)0x0000000103b6f800: objc_msgSend
    0x103658967 <+71>:  movq   %rax, -0x88(%rbp)
    0x10365896e <+78>:  callq  *%rax
    0x103658970 <+80>:  movq   %rax, %rdi
    0x103658973 <+83>:  callq  0x103659896               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x103658978 <+88>:  movq   %rax, -0x20(%rbp)         ; // NSString* b = [NSString stringWithUTF8String:"abc"];
    0x10365897c <+92>:  movq   0x35a5(%rip), %rsi        ; "stringByAppendingString:"
    0x103658983 <+99>:  leaq   0x27b6(%rip), %rdi        ; @"ab"
    0x10365898a <+106>: leaq   0x27cf(%rip), %rdx        ; @"'c'"
    0x103658991 <+113>: movq   -0x88(%rbp), %rax
    0x103658998 <+120>: callq  *%rax
    0x10365899a <+122>: movq   %rax, %rdi
    0x10365899d <+125>: callq  0x103659896               ; symbol stub for: objc_retainAutoreleasedReturnValue
    0x1036589a2 <+130>: movq   %rax, -0x28(%rbp)         ; // NSString* c = [@"ab" stringByAppendingString:@"c"];

 

毫无悬念__strong三人组sa,sb,sc,在初始值也都使出了objc_retain。

__strong id sa = a;

__strong id sb = b;

__strong id sc = c;

 0x1036589a6 <+134>: movq   -0x18(%rbp), %rdi
    0x1036589aa <+138>: movq   -0x80(%rbp), %rax
    0x1036589ae <+142>: callq  *%rax                     ; objc_retain
    0x1036589b0 <+144>: movq   %rax, -0x30(%rbp)         ; // __strong id sa = a;
    0x1036589b4 <+148>: movq   -0x20(%rbp), %rdi
    0x1036589b8 <+152>: movq   -0x80(%rbp), %rax
    0x1036589bc <+156>: callq  *%rax
    0x1036589be <+158>: movq   %rax, -0x38(%rbp)         ; // __strong id sb = b;
    0x1036589c2 <+162>: movq   -0x28(%rbp), %rdi
    0x1036589c6 <+166>: movq   -0x80(%rbp), %rax
    0x1036589ca <+170>: callq  *%rax
    0x1036589cc <+172>: movq   %rax, -0x40(%rbp)         ; // __strong id sc = c;

 

同是__strong阵营的a,b,c和sa,sb,sc,它们交手赋值,它们使出了objc_storeStrong。在它们掌心和掌心对上的那一刻,a已经将旧时的它release掉,同时用写轮眼retain了sa。

a = sa;

b = sb;

c = sc;

    0x1036589d0 <+176>: movq   -0x30(%rbp), %rsi
    0x1036589d4 <+180>: leaq   -0x18(%rbp), %rdi
    0x1036589d8 <+184>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x1036589dd <+189>: movq   -0x38(%rbp), %rsi
    0x1036589e1 <+193>: leaq   -0x20(%rbp), %rdi
    0x1036589e5 <+197>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x1036589ea <+202>: movq   -0x40(%rbp), %rsi
    0x1036589ee <+206>: leaq   -0x28(%rbp), %rdi
    0x1036589f2 <+210>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong

 

不好,__strong遭遇上__weak! __weak的wa,wb,wc纷纷要上标记使出了objc_initWeak。它们伪装成了a,sb,c。就是说它们存储的地址一样,*(int64*)&a == *(int64*)&wa,但是它们的引用操作却不一样。

__weak id wa = a;

__weak id wb = sb;

__weak id wc = c;

    0x1036589f7 <+215>: movq   -0x18(%rbp), %rsi
    0x1036589fb <+219>: leaq   -0x48(%rbp), %rdi
    0x1036589ff <+223>: callq  0x103659866               ; symbol stub for: objc_initWeak
    0x103658a04 <+228>: movq   -0x38(%rbp), %rsi
    0x103658a08 <+232>: leaq   -0x50(%rbp), %rdi
    0x103658a0c <+236>: movq   %rax, -0x90(%rbp)
    0x103658a13 <+243>: callq  0x103659866               ; symbol stub for: objc_initWeak
    0x103658a18 <+248>: movq   -0x28(%rbp), %rsi
    0x103658a1c <+252>: leaq   -0x58(%rbp), %rdx
    0x103658a20 <+256>: movq   %rdx, %rdi
    0x103658a23 <+259>: movq   %rax, -0x98(%rbp)
    0x103658a2a <+266>: movq   %rdx, -0xa0(%rbp)
    0x103658a31 <+273>: callq  0x103659866               ; symbol stub for: objc_initWeak

 

不要转台不要收快递交水费,__strong还招啦。__strong的sc对__weak的wc不可以直接使出retain,而必须让__weak先使用objc_loadWeakRetained。得手后的sc马上objc_release旧型态,才能对wc伪装的实体进行retain。

相反__weak的wc对__strong的sb出手则可以直接使出一招objc_storeWeak,然后伪装成了sb。

sc = wc;

wc = sb;

    0x103658a36 <+278>: movq   -0xa0(%rbp), %rdi
    0x103658a3d <+285>: movq   %rax, -0xa8(%rbp)
    0x103658a44 <+292>: callq  0x10365986c               ; symbol stub for: objc_loadWeakRetained
    0x103658a49 <+297>: movq   -0x40(%rbp), %rdi
    0x103658a4d <+301>: movq   %rax, -0x40(%rbp)
    0x103658a51 <+305>: movq   0x25d8(%rip), %rax        ; (void *)0x0000000103b6cd70: objc_release
    0x103658a58 <+312>: callq  *%rax
    0x103658a5a <+314>: movq   -0x38(%rbp), %rsi
    0x103658a5e <+318>: movq   -0xa0(%rbp), %rdi
    0x103658a65 <+325>: callq  0x1036598a8               ; symbol stub for: objc_storeWeak

 

wc要用它刚获得到的新伪装型态,对sc还招,wc要使用sb的技能-isEqualToString:,终究变化系不能直接使出强化系,必须通过objc_loadWeakRetained转系,然后大喝一声,使出了崩天塌地的一招-isEqualToString:,一掌拍向sc。

sc施展出凌波微步,想避开wc,但还是中了掌, wc返回了YES。最后wc还要用objc_release收功散气,脱离sb型态。

[wc isEqualToString:sc];    

    0x103658a6a <+330>: movq   -0xa0(%rbp), %rdi
    0x103658a71 <+337>: movq   %rax, -0xb0(%rbp)
    0x103658a78 <+344>: callq  0x10365986c               ; symbol stub for: objc_loadWeakRetained
    0x103658a7d <+349>: movq   %rax, %rdx
    0x103658a80 <+352>: movq   -0x40(%rbp), %rsi
    0x103658a84 <+356>: movq   0x34b5(%rip), %rdi        ; "isEqualToString:"
    0x103658a8b <+363>: movq   %rdi, -0xb8(%rbp)
    0x103658a92 <+370>: movq   %rax, %rdi
    0x103658a95 <+373>: movq   -0xb8(%rbp), %rax
    0x103658a9c <+380>: movq   %rsi, -0xc0(%rbp)
    0x103658aa3 <+387>: movq   %rax, %rsi
    0x103658aa6 <+390>: movq   -0xc0(%rbp), %rcx
    0x103658aad <+397>: movq   %rdx, -0xc8(%rbp)
    0x103658ab4 <+404>: movq   %rcx, %rdx
    0x103658ab7 <+407>: movq   -0x88(%rbp), %r8
    0x103658abe <+414>: callq  *%r8
    0x103658ac1 <+417>: movb   %al, -0xc9(%rbp)
    0x103658ac7 <+423>: jmp    0x103658acc               ; <+428> at ViewController.m:100
    0x103658acc <+428>: movq   0x255d(%rip), %rax        ; (void *)0x0000000103b6cd70: objc_release
    0x103658ad3 <+435>: movq   -0xc8(%rbp), %rdi
    0x103658ada <+442>: callq  *%rax

 

对于没有autorelease过的objc对象,__strong是不会使出objc_retain,

__strong NSString* sx = [[NSString alloc] initWithString:@"abc"];

    0x103658adc <+444>: movq   0x34c5(%rip), %rdi        ; (void *)0x0000000103970b20: NSString
    0x103658ae3 <+451>: movq   0x345e(%rip), %rsi        ; "alloc"
    0x103658aea <+458>: movq   0x2537(%rip), %rax        ; (void *)0x0000000103b6f800: objc_msgSend
    0x103658af1 <+465>: callq  *%rax
    0x103658af3 <+467>: movq   %rax, -0xd8(%rbp)
    0x103658afa <+474>: jmp    0x103658aff               ; <+479> at ViewController.m:102
    0x103658aff <+479>: movq   0x344a(%rip), %rsi        ; "initWithString:"
    0x103658b06 <+486>: leaq   0x2613(%rip), %rdx        ; @"abc"
    0x103658b0d <+493>: movq   0x2514(%rip), %rax        ; (void *)0x0000000103b6f800: objc_msgSend
    0x103658b14 <+500>: movq   -0xd8(%rbp), %rdi
    0x103658b1b <+507>: callq  *%rax
    0x103658b1d <+509>: movq   %rax, -0xe0(%rbp)
    0x103658b24 <+516>: jmp    0x103658b29               ; <+521> at ViewController.m:102
    0x103658b29 <+521>: movq   -0xe0(%rbp), %rax
    0x103658b30 <+528>: movq   %rax, -0x70(%rbp)

NSString* x = [[NSString alloc] initWithString:@"abc"];

    0x103658b34 <+532>: movq   0x346d(%rip), %rdi        ; (void *)0x0000000103970b20: NSString
    0x103658b3b <+539>: movq   0x3406(%rip), %rsi        ; "alloc"
    0x103658b42 <+546>: movq   0x24df(%rip), %rcx        ; (void *)0x0000000103b6f800: objc_msgSend
    0x103658b49 <+553>: callq  *%rcx
    0x103658b4b <+555>: movq   %rax, -0xe8(%rbp)
    0x103658b52 <+562>: jmp    0x103658b57               ; <+567> at ViewController.m:103
    0x103658b57 <+567>: movq   0x33f2(%rip), %rsi        ; "initWithString:"
    0x103658b5e <+574>: leaq   0x25bb(%rip), %rdx        ; @"abc"
    0x103658b65 <+581>: movq   0x24bc(%rip), %rax        ; (void *)0x0000000103b6f800: objc_msgSend
    0x103658b6c <+588>: movq   -0xe8(%rbp), %rdi
    0x103658b73 <+595>: callq  *%rax
    0x103658b75 <+597>: movq   %rax, -0xf0(%rbp)
    0x103658b7c <+604>: jmp    0x103658b81               ; <+609> at ViewController.m:103
    0x103658b89 <+617>: movq   -0xf0(%rbp), %rdx
    0x103658b90 <+624>: movq   %rdx, -0x78(%rbp)

 

在谢幕时,它们离开作用范围,(它们是__strong<Tptr>和__weak<Tptr>,而不是它们指向的实例对象。)它们作为c++对象析构了。析构请参看上面我给出的伪代码。

pointers destructures   

    0x103658b81 <+609>: xorl   %eax, %eax
    0x103658b83 <+611>: movl   %eax, %esi
    0x103658b85 <+613>: leaq   -0x78(%rbp), %rcx
   
    0x103658b94 <+628>: movq   %rcx, %rdi
    0x103658b97 <+631>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658b9c <+636>: xorl   %eax, %eax
    0x103658b9e <+638>: movl   %eax, %esi
    0x103658ba0 <+640>: leaq   -0x70(%rbp), %rcx
    0x103658ba4 <+644>: movq   %rcx, %rdi
    0x103658ba7 <+647>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658bac <+652>: leaq   -0x58(%rbp), %rdi
    0x103658bb0 <+656>: callq  0x103659860               ; symbol stub for: objc_destroyWeak
    0x103658bb5 <+661>: leaq   -0x50(%rbp), %rdi
    0x103658bb9 <+665>: callq  0x103659860               ; symbol stub for: objc_destroyWeak
    0x103658bbe <+670>: leaq   -0x48(%rbp), %rdi
    0x103658bc2 <+674>: callq  0x103659860               ; symbol stub for: objc_destroyWeak
    0x103658bc7 <+679>: leaq   -0x40(%rbp), %rdi
    0x103658bcb <+683>: xorl   %eax, %eax
    0x103658bcd <+685>: movl   %eax, %esi
    0x103658bcf <+687>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658bd4 <+692>: leaq   -0x38(%rbp), %rdi
    0x103658bd8 <+696>: xorl   %eax, %eax
    0x103658bda <+698>: movl   %eax, %esi
    0x103658bdc <+700>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658be1 <+705>: leaq   -0x30(%rbp), %rdi
    0x103658be5 <+709>: xorl   %eax, %eax
    0x103658be7 <+711>: movl   %eax, %esi
    0x103658be9 <+713>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658bee <+718>: xorl   %eax, %eax
    0x103658bf0 <+720>: movl   %eax, %esi
    0x103658bf2 <+722>: leaq   -0x28(%rbp), %rcx
    0x103658bf6 <+726>: movq   %rcx, %rdi
    0x103658bf9 <+729>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658bfe <+734>: xorl   %eax, %eax
    0x103658c00 <+736>: movl   %eax, %esi
    0x103658c02 <+738>: leaq   -0x20(%rbp), %rcx
    0x103658c06 <+742>: movq   %rcx, %rdi
    0x103658c09 <+745>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658c0e <+750>: xorl   %eax, %eax
    0x103658c10 <+752>: movl   %eax, %esi
    0x103658c12 <+754>: leaq   -0x18(%rbp), %rcx
    0x103658c16 <+758>: movq   %rcx, %rdi
    0x103658c19 <+761>: callq  0x1036598a2               ; symbol stub for: objc_storeStrong
    0x103658c1e <+766>: addq   $0xf0, %rsp
    0x103658c25 <+773>: popq   %rbp
    0x103658c26 <+774>: retq   

 

花絮送出__weak和__weak的NG片段,wb被逼使用objc_loadWeakRetained,wa才能对其真身使出objc_storeWeak,然后wb使用objc_release结束真身型态。__weak作为operator=的操作数或msgSend的接收者时,都必须进行loadWeakRetained,结束之时马上release。

wa = wb;

    0x10c47bb51 <+609>: leaq   -0x50(%rbp), %rdi
    0x10c47bb60 <+624>: callq  0x10c47c86c               ; symbol stub for: objc_loadWeakRetained
    0x10c47bb65 <+629>: leaq   -0x48(%rbp), %rdi
    0x10c47bb69 <+633>: movq   %rax, %rsi
    0x10c47bb6c <+636>: movq   %rax, -0xf8(%rbp)
    0x10c47bb73 <+643>: callq  0x10c47c8a8               ; symbol stub for: objc_storeWeak
    0x10c47bb78 <+648>: movq   -0xf8(%rbp), %rdi
    0x10c47bb7f <+655>: movq   %rax, -0x100(%rbp)
    0x10c47bb86 <+662>: callq  0x10c47c87e               ; symbol stub for: objc_release

 

最后多谢大家观看本期节目,希望看得开心无聊得欢乐,多谢收看。

下一篇:《反汇编objc分析__block》

posted on 2016-01-11 11:05  bbqz007  阅读(1671)  评论(0编辑  收藏  举报