OC對象的本質(zhì)(上) —— OC對象的底層實現(xiàn)原理

OC對象的本質(zhì)(上):OC對象的底層實現(xiàn)原理
OC對象的本質(zhì)(中):OC對象的種類
OC對象的本質(zhì)(下):詳解isa&superclass指針

一個NSObject對象占用多少內(nèi)存?

Objective-C的本質(zhì)

平時我們編寫的OC代碼绣硝,底層實現(xiàn)都是C/C++代碼

Objective-C --> C/C++ --> 匯編語言 --> 機器碼

所以O(shè)bjective-C的面向?qū)ο蠖际腔贑/C++的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的,所以我們可以將Objective-C代碼轉(zhuǎn)換成C/C++代碼面徽,來研究OC對象的本質(zhì)。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
    }
    return 0;
}

我們在main函數(shù)里面定義一個簡單對象刹碾,然后通過 clang -rewrite-objc main.m -o main.cpp命令啼染,將main.m文件進行重寫,即可轉(zhuǎn)換出對應(yīng)的C/C++代碼投剥。但是可以看到一個問題师脂,就是轉(zhuǎn)換出來的文件過長,將近10w行江锨。


因為不同平臺支持的代碼不同(Windows/Mac/iOS)吃警,那么同樣一句OC代碼,經(jīng)過編譯啄育,轉(zhuǎn)成C/C++代碼酌心,以及最終的匯編碼,是不一樣的挑豌,匯編指令嚴(yán)重依賴平臺環(huán)境安券。
我們當(dāng)前關(guān)注iOS開發(fā),所以氓英,我們只需要生成iOS支持的C/C++代碼侯勉。因此,可以使用如下命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc <OC源文件> -o <輸出的cpp文件>
-sdk:指定sdk
-arch:指定機器cpu架構(gòu)(模擬器-i386铝阐、32bit址貌、64bit-arm64 )
如果需要鏈接其他框架,使用-framework參數(shù)徘键,比如-framework UIKit
一般我們手機都已經(jīng)普及arm64芳誓,所以這里的架構(gòu)參數(shù)用arm64,生成的cpp代碼如下


接下來啊鸭,我們查看一下main_arm64.cpp源文件锹淌,如果熟悉這個文件,你將會發(fā)現(xiàn)這么一個結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa;
};

我們再來對比看一下NSObject頭文件的定義

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end

簡化一下赠制,就是

@interface NSObject  {
    Class isa ;
}
@end

是不是猜到點什么了赂摆?沒錯,struct NSObject_IMPL其實就是NSObject的底層結(jié)構(gòu)钟些,或者說底層實現(xiàn)烟号。換個角度理解,可以說C/C++的結(jié)構(gòu)體類型支撐了OC的面相對象政恍。

點進Class的定義汪拥,我們可以看到 是typedef struct objc_class *Class;

Class isa; 等價于 struct objc_class *isa;

所以NSObject對象內(nèi)部就是放了一個名叫isa的指針,指向了一個結(jié)構(gòu)體 struct objc_class篙耗。

總結(jié)一:一個OC對象在內(nèi)存中是如何布局的迫筑?


猜想:NSObject對象的底層就是一個包含了一個指針的結(jié)構(gòu)體宪赶,那么它的大小是不是就是8字節(jié)(64位下指針類型占8個字節(jié))?
為了驗證猜想脯燃,我們需要借助runtime提供的一些工具搂妻,導(dǎo)入runtime頭文件,class_getInstanceSize ()方法可以計算一個類的實例對象所實際需要的的空間大小

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject對象的大小:%zd",size);
    }
    return 0;
}

結(jié)果是



完美驗證辕棚,it's over欲主,let's go home!







等等逝嚎,就這么簡單扁瓢?確定嗎?答案是否定的~~~
介紹另一個庫#import <malloc/malloc.h>补君,其下有個方法 malloc_size()涤妒,該函數(shù)的參數(shù)是一個指針,可以計算所傳入指針 所指向內(nèi)存空間的大小赚哗。我們來用一下

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject實例對象的大兴稀:%zd",size);
        size_t size2 = malloc_size((__bridge const void *)(obj));
        NSLog(@"對象obj所指向的的內(nèi)存空間大小:%zd",size2);
    }
    return 0;
}

結(jié)果是16屿储,如何解釋呢贿讹?

想要真正弄清楚其中的緣由,就需要去蘋果官方的開源代碼里面去一探究竟了够掠。蘋果的開源代請看這里民褂。
先看一下class_getInstanceSize的實現(xiàn)。我們需要進到objc4/文件里面下載一份最新的源碼疯潭,我當(dāng)前最新的版本是objc4-750.1.tar.gz赊堪。下載解壓之后,打開工程竖哩,就可以查看runtime的實現(xiàn)源碼哭廉。
搜索class_getInstanceSize找到實現(xiàn)代碼

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

再點進alignedInstanceSize方法的實現(xiàn)

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

可以看到該方法的注釋說明Class's ivar size rounded up to a pointer-size boundary.,意思就是獲得類的成員變量的大小相叁,其實也就是計算類所對應(yīng)的底層結(jié)構(gòu)體的大小遵绰,注意后面的這個rounded up to a pointer-size boundary指的是系統(tǒng)在為類的結(jié)構(gòu)體分配內(nèi)存時所進行的內(nèi)存對齊,要以一個指針的長度作為對齊系數(shù)增淹,64位系統(tǒng)指針長度(字長)是8個字節(jié)椿访,那么返回的結(jié)果肯定是8的最小整數(shù)倍。為什么需要用指針長度作為對齊系數(shù)呢虑润?因為類所對應(yīng)的結(jié)構(gòu)體成玫,在頭部的肯定是一個isa指針,所以指針肯定是該結(jié)構(gòu)體中最大的基本數(shù)據(jù)類型,所以根據(jù)結(jié)構(gòu)體的內(nèi)存對齊規(guī)則哭当,才做此設(shè)定猪腕。如果對這里有疑惑的話,請先復(fù)習(xí)一下有關(guān)內(nèi)存對齊的知識荣病,便一目了然了。
所以class_getInstanceSize方法渗柿,可以幫我們獲取一個類的的實例對象所對應(yīng)的結(jié)構(gòu)體的實際大小个盆。

我們再從alloc方法探究一下,alloc方法里面實際上是AllocWithZone方法朵栖,我們在objc源碼工程里面搜索一下颊亮,可以在Object.mm文件里面找到一個_objc_rootAllocWithZone方法。

id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;

#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif

    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}

再點進里面的關(guān)鍵方法class_createInstance的實現(xiàn)看一下

id  class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

繼續(xù)點進_class_createInstanceFromZone方法

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

這個方法有點長陨溅,有時分析一個方法终惑,不要過分拘泥細(xì)節(jié),先針對我們尋找的問題门扇,找到關(guān)鍵點雹有,像這個比較長的方法,我們知道臼寄,它的主要功能就是創(chuàng)建一個實例霸奕,為其開辟內(nèi)存空間,我們可以發(fā)現(xiàn)中間的這句代碼obj = (id)calloc(1, size);吉拳,是在分配內(nèi)存质帅,這里的size是需要分配的內(nèi)存的大小,那這句應(yīng)該就是為對象開辟內(nèi)存的核心代碼留攒,再看它里面的參數(shù)size煤惩,我們能在上兩行代碼中找到size_t size = cls->instanceSize(extraBytes);,于是我們繼續(xù)點進instanceSize看看

size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

翻譯一下這句注//CF requires all objects be at least 16 bytes.我們就明白了炼邀,CF作出了硬性的規(guī)定:當(dāng)創(chuàng)建一個實例對象的時候魄揉,為其分配的空間不能小于16個字節(jié),為什么這么規(guī)定呢拭宁,我個人目前的理解是這可能就相當(dāng)于一種開發(fā)規(guī)范什猖,或者對于CF框架內(nèi)部的一些實現(xiàn)提供的規(guī)范。
這個size_t instanceSize(size_t extraBytes)返回的字節(jié)數(shù)红淡,其實就是為 為一個類創(chuàng)建實例對象所需要分配的內(nèi)存空間不狮。這里我們的NSObject類創(chuàng)建一個實例對象,就分配了16個字節(jié)在旱。
我們在點進上面代碼中的alignedInstanceSize方法

// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

這不就是我們上面分析class_getInstanceSize方法里面看到的那個alignedInstanceSize嘛摇零。

總結(jié)二:class_getInstanceSize&malloc_size的區(qū)別
  • class_getInstanceSize:獲取一個objc類的實例的實際大小,這個大小可以理解為創(chuàng)建這個實例對象至少需要的空間(系統(tǒng)實際為這個對象分配的空間可能會比這個大桶蝎,這是出于系統(tǒng)內(nèi)存對齊的原因)驻仅。
  • malloc_size:得到一個指針?biāo)赶虻膬?nèi)存空間的大小谅畅。我們的OC對象就是一個指針,利用這個函數(shù)噪服,我們可以得到該對象所占用的內(nèi)存大小毡泻,也就是系統(tǒng)為這個對象(指針)所指向?qū)ο笏鶎嶋H分配的內(nèi)存大小。
    sizeof():獲取一個類型或者變量所占用的存儲空間粘优,這是一個運算符仇味。
  • [NSObject alloc]之后,系統(tǒng)為其分配了16個字節(jié)的內(nèi)存雹顺,最終obj對象(也就是struct NSObject_IMPL結(jié)構(gòu)體)丹墨,實際使用了其中的8個字節(jié)內(nèi)存,(也就是其內(nèi)部的那個isa指針?biāo)玫?個字節(jié)嬉愧,這里我們是在64位系統(tǒng)為前提下來說的)

關(guān)于運算符和函數(shù)的一些對比理解

  • 函數(shù)在編譯完之后贩挣,是可以在程序運行階段被調(diào)用的,有調(diào)用行為的發(fā)生
  • 運算符則是在編譯按一刻没酣,直接被替換成運算后的結(jié)果常量王财,跟宏定義有些類似,不存在調(diào)用的行為裕便,所以效率非常高

更為復(fù)雜的自定義類

我們開發(fā)中會自定義各種各樣的類搪搏,基本上都是NSObject的子類。更為復(fù)雜的子類對象的內(nèi)存布局又是如何的呢闪金?我們新建一個NSObject的子類Student疯溺,并為其增加一些成員變量

@interface Student : NSObject
{
   @public
    int _age;
    int _no;
}

@end

@implementation Student

@end

使用我們之前介紹過的方法,查看一下這個類的底層實現(xiàn)代碼

struct NSObject_IMPL {
    Class isa;
};

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _no;  
    
};

我們發(fā)現(xiàn)其實Student的底層結(jié)構(gòu)里哎垦,包含了它的成員變量囱嫩,還有一個NSObject_IMPL結(jié)構(gòu)體變量,也就是它的父類的結(jié)構(gòu)體漏设。根據(jù)我們上面的總結(jié)墨闲,NSObject_IMPL結(jié)構(gòu)體需要的空間是8字節(jié),但是系統(tǒng)給NSObject對象實際分配的內(nèi)存是16字節(jié)郑口,那么這里Student的底層結(jié)構(gòu)體里面的成員變量NSObject_IMPL應(yīng)該會得到多少的內(nèi)存分配呢鸳碧?我們驗證一下。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        //獲取`NSObject`類的實例對象的成員變量所占用的大小
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject實例對象的大腥浴:%zd",size);
        //獲取obj所指向的內(nèi)存空間的大小
        size_t size2 = malloc_size((__bridge const void *)(obj));
        NSLog(@"對象obj所指向的的內(nèi)存空間大姓袄搿:%zd",size2);
        
        Student * std = [[Student alloc]init];
        size_t size3 = class_getInstanceSize([Student class]);
        NSLog(@"Student實例對象的大小:%zd",size3);
        size_t size4 = malloc_size((__bridge const void *)(std));
        NSLog(@"對象std所指向的的內(nèi)存空間大衅柜伞:%zd",size4);
    }
    return 0;
}


從結(jié)果可以看出套利,Student類的底層結(jié)構(gòu)體等同于

struct Student_IMPL {
    Class isa;
    int _age;
    int _no;      
};

總結(jié)一下就是,一個子類的底層結(jié)構(gòu)體,相當(dāng)于 其父類結(jié)構(gòu)體里面的所有成員變量 + 該子類自身定義的成員變量 所組成的一個結(jié)構(gòu)體肉迫。
出于嚴(yán)謹(jǐn)验辞,我又給Student類多加了幾個成員變量,驗證我的猜想喊衫。

@interface Student : NSObject
{
   @public
    int _age;
    int _no;
    int _grade;
}
image.png

貌似是對的了跌造,但是為什么用malloc_size得到std所被分配的內(nèi)存是32?再來一發(fā)試試

@interface Student : NSObject
{

   @public
    //父類的isa還會占用8個字節(jié)
    int _age;//4字節(jié)
    int _no;//4字節(jié)
    int _grade;//4字節(jié)
    int *p1;//8字節(jié)
    int *p2;//8字節(jié) 
}

Student結(jié)構(gòu)體所有成員變量所需要的總空間為 36字節(jié)族购,根據(jù)內(nèi)存對齊原則壳贪,最后結(jié)構(gòu)體所需要的空間應(yīng)該是8的倍數(shù),那應(yīng)該就是40联四,我們看一下結(jié)果

從結(jié)果看沒錯撑碴,但是同時也發(fā)現(xiàn)了一個規(guī)律撑教,隨著std對象成員變量的增加朝墩,系統(tǒng)為Student對象std分配的內(nèi)存空間總是以16的倍數(shù)增加(16~32~48......),我們之前分析源碼好像沒看到有做這個設(shè)定


其實上面這個方法只是可以用來計算一個結(jié)構(gòu)體對象所實際需要的內(nèi)存大小伟姐。 [update]其實instanceSize()-->alignedInstanceSize()只是可以用來計算一個結(jié)構(gòu)體對象理論上(按照內(nèi)存對其規(guī)則)所需要分配的內(nèi)存大小收苏。

真正給實例對象完成分配內(nèi)存操作的是下面這個方法calloc()


這個方法位于蘋果源碼的libmalloc文件夾中。但是里面的代碼再往下深究愤兵,介于我目前的知識儲備以及專業(yè)出身(數(shù)學(xué)專業(yè))鹿霸,還是困難比較大。好在從一些大神那里得到了指點秆乳。
剛才文章開始懦鼠,我們討論到了結(jié)構(gòu)體的內(nèi)存對齊,這是針對數(shù)據(jù)結(jié)構(gòu)而言的屹堰。從系統(tǒng)層面來說肛冶,就以蘋果系統(tǒng)而言,出于對內(nèi)存管理和訪問效率最優(yōu)化的需要扯键,會實現(xiàn)在內(nèi)存中規(guī)劃出很多塊睦袖,這些塊有大有小,但都是16的倍數(shù)荣刑,比如有的是32馅笙,有的是48,在libmalloc源碼的nano_zone.h里面有這么一段代碼

#define NANO_MAX_SIZE    256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */

NANO是源碼庫里面的其中一種內(nèi)存分配方法厉亏,類似的還有frozen董习、legacymagazine爱只、purgeable阱飘。

這些是蘋果基于各種場景優(yōu)化需求而設(shè)定的對應(yīng)的內(nèi)存管理相關(guān)的庫,暫時不用對其過分解讀。
上面的NANO_MAX_SIZE解釋中有個詞Buckets sized沥匈,就是蘋果事先規(guī)劃好的內(nèi)存塊的大小要求蔗喂,針對nano,內(nèi)存塊都被設(shè)定成16的倍數(shù)高帖,并且最大值是256缰儿。舉個例子,如果一個對象結(jié)構(gòu)體需要46個字節(jié)散址,那么系統(tǒng)會找一塊48字節(jié)的內(nèi)存塊分配給它用乖阵,如果另一個結(jié)構(gòu)體需要58個字節(jié),那么系統(tǒng)會找一塊64字節(jié)的內(nèi)存塊分配給它用预麸。
到這里瞪浸,應(yīng)該就可以基本上解釋清楚,為什么剛才student結(jié)構(gòu)需要40個字節(jié)的時候吏祸,被分配到的內(nèi)存大小確實48個字節(jié)对蒲。至此,針對一個NSObject對象占用內(nèi)存的問題贡翘,以及延伸出來的內(nèi)存布局蹈矮,以及其子類的占內(nèi)存問題,應(yīng)該就都可以得到解答了鸣驱。


OC對象的本質(zhì)(上):OC對象的底層實現(xiàn)
OC對象的本質(zhì)(中):OC對象的分類
OC對象的本質(zhì)(下):詳解isa&superclass指針

面試題解答

  • 一個NSObject對象占用多少內(nèi)存泛鸟?
    1)系統(tǒng)分配了16字節(jié)給NSObject對象(通過malloc_size函數(shù)可以獲得)
    2)NSObject對象內(nèi)部只使用了8個字節(jié)的空間,用來存放isa指針變量(64位系統(tǒng)下踊东,可以通過class_getInstanceSize函數(shù)獲得)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末北滥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子闸翅,更是在濱河造成了極大的恐慌再芋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缎脾,死亡現(xiàn)場離奇詭異祝闻,居然都是意外死亡,警方通過查閱死者的電腦和手機遗菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門联喘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辙纬,你說我怎么就攤上這事豁遭。” “怎么了贺拣?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵蓖谢,是天一觀的道長捂蕴。 經(jīng)常有香客問我,道長闪幽,這世上最難降的妖魔是什么啥辨? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮盯腌,結(jié)果婚禮上溉知,老公的妹妹穿的比我還像新娘。我一直安慰自己腕够,他們只是感情好级乍,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帚湘,像睡著了一般玫荣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上大诸,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天捅厂,我揣著相機與錄音,去河邊找鬼底挫。 笑死恒傻,一個胖子當(dāng)著我的面吹牛脸侥,可吹牛的內(nèi)容都是我干的建邓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼睁枕,長吁一口氣:“原來是場噩夢啊……” “哼官边!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起外遇,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤注簿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跳仿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诡渴,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年菲语,在試婚紗的時候發(fā)現(xiàn)自己被綠了妄辩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡山上,死狀恐怖眼耀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佩憾,我是刑警寧澤哮伟,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布干花,位于F島的核電站,受9級特大地震影響楞黄,放射性物質(zhì)發(fā)生泄漏池凄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鬼廓、第九天 我趴在偏房一處隱蔽的房頂上張望修赞。 院中可真熱鬧,春花似錦桑阶、人聲如沸柏副。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽割择。三九已至,卻和暖如春萎河,著一層夾襖步出監(jiān)牢的瞬間荔泳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工虐杯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留玛歌,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓擎椰,卻偏偏與公主長得像支子,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子达舒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容