6-1 編譯器

編譯器做些什么?

本文主要探討一下編譯器主要做些什么恩掷,以及如何有效的利用編譯器帚桩。

簡單的說特漩,編譯器有兩個(gè)職責(zé):把 Objective-C 代碼轉(zhuǎn)化成低級(jí)代碼吧雹,以及對(duì)代碼做分析,確保代碼中沒有任何明顯的錯(cuò)誤涂身。

現(xiàn)在雄卷,Xcode 的默認(rèn)編譯器是 clang。本文中我們提到的編譯器都表示 clang蛤售。clang 的功能是首先對(duì) Objective-C 代碼做分析檢查丁鹉,然后將其轉(zhuǎn)換為低級(jí)的類匯編代碼:LLVM Intermediate Representation(LLVM 中間表達(dá)碼)。接著 LLVM 會(huì)執(zhí)行相關(guān)指令將 LLVM IR 編譯成目標(biāo)平臺(tái)上的本地字節(jié)碼悴能,這個(gè)過程的完成方式可以是即時(shí)編譯 (Just-in-time)揣钦,或在編譯的時(shí)候完成。

LLVM 指令的一個(gè)好處就是可以在支持 LLVM 的任意平臺(tái)上生成和運(yùn)行 LLVM 指令漠酿。例如冯凹,你寫的一個(gè) iOS app, 它可以自動(dòng)的運(yùn)行在兩個(gè)完全不同的架構(gòu)(Inter 和 ARM)上,LLVM 會(huì)根據(jù)不同的平臺(tái)將 IR 碼轉(zhuǎn)換為對(duì)應(yīng)的本地字節(jié)碼炒嘲。

LLVM 的優(yōu)點(diǎn)主要得益于它的三層式架構(gòu) -- 第一層支持多種語言作為輸入(例如 C, ObjectiveC, C++ 和 Haskell)宇姚,第二層是一個(gè)共享式的優(yōu)化器(對(duì) LLVM IR 做優(yōu)化處理),第三層是許多不同的目標(biāo)平臺(tái)(例如 Intel, ARM 和 PowerPC)夫凸。在這三層式的架構(gòu)中浑劳,如果你想要添加一門語言到 LLVM 中,那么可以把重要精力集中到第一層上夭拌,如果想要增加另外一個(gè)目標(biāo)平臺(tái)魔熏,那么你沒必要過多的考慮輸入語言。在書The Architecture of Open Source Applications中 LLVM 的創(chuàng)建者 (Chris Lattner) 寫了一章很棒的內(nèi)容:關(guān)于LLVM 架構(gòu)鸽扁。

在編譯一個(gè)源文件時(shí)道逗,編譯器的處理過程分為幾個(gè)階段。要想查看編譯hello.m源文件需要幾個(gè)不同的階段献烦,我們可以讓通過 clang 命令觀察:

本文我們將重點(diǎn)關(guān)注第一階段和第二階段滓窍。在文章Mach-O Executables中,Daniel 會(huì)對(duì)第三階段和第四階段進(jìn)行闡述巩那。

預(yù)處理

每當(dāng)編源譯文件的時(shí)候吏夯,編譯器首先做的是一些預(yù)處理工作此蜈。比如預(yù)處理器會(huì)處理源文件中的宏定義,將代碼中的宏用其對(duì)應(yīng)定義的具體內(nèi)容進(jìn)行替換噪生。

例如裆赵,如果在源文件中出現(xiàn)下述代碼:

#import

預(yù)處理器對(duì)這行代碼的處理是用 Foundation.h 文件中的內(nèi)容去替換這行代碼,如果 Foundation.h 中也使用了類似的宏引入跺嗽,則會(huì)按照同樣的處理方式用各個(gè)宏對(duì)應(yīng)的真正代碼進(jìn)行逐級(jí)替代战授。

這也就是為什么人們主張頭文件最好盡量少的去引入其他的類或庫,因?yàn)橐氲臇|西越多桨嫁,編譯器需要做的處理就越多植兰。例如,在頭文件中用:

@classMyClass;

代替:

#import"MyClass.h"

這么寫是告訴編譯器 MyClass 是一個(gè)類璃吧,并且在 .m 實(shí)現(xiàn)文件中可以通過 importMyClass.h的方式來使用它楣导。

假設(shè)我們寫了一個(gè)簡單的 C 程序hello.c:


然后給上面的代碼執(zhí)行以下預(yù)處理命令,看看是什么效果:

clang -Ehello.c| less

接下來看看處理后的代碼畜挨,一共是 401 行筒繁。如果將如下一行代碼添加到上面代碼的頂部::

#import

再執(zhí)行一下上面的預(yù)處理命令,處理后的文件代碼行數(shù)暴增至 89,839 行巴元。這個(gè)數(shù)字比某些操作系統(tǒng)的總代碼行數(shù)還要多毡咏。

幸好,目前的情況已經(jīng)改善許多了:引入了模塊 - modules功能逮刨,這使預(yù)處理變得更加的高級(jí)呕缭。

自定義宏

我們來看看另外一種情形定義或者使用自定義宏,比如定義了如下宏:

#define MY_CONSTANT 4

那么禀忆,凡是在此行宏定義作用域內(nèi)臊旭,輸入了MY_CONSTANT,在預(yù)處理過程中MY_CONSTANT都會(huì)被替換成4箩退。我們定義的宏也是可以攜帶參數(shù)的离熏, 比如:

#defineMY_MACRO(x)x

鑒于本文的內(nèi)容所限,就不對(duì)強(qiáng)大的預(yù)處理做更多戴涝、更全面的展開討論了滋戳。但是還是要強(qiáng)調(diào)一點(diǎn),建議大家不要在需要預(yù)處理的代碼中加入內(nèi)聯(lián)代碼邏輯啥刻。

例如奸鸯,下面這段代碼,這樣用沒什么問題:

但是如果換成這么寫:

用clang max.c編譯一下可帽,結(jié)果是:

largest:201

i:202

用clang -E max.c進(jìn)行宏展開的預(yù)處理結(jié)果是如下所示:

本例是典型的宏使用不當(dāng)娄涩,而且通常這類問題非常隱蔽且難以 debug 。針對(duì)本例這類情況,最好使用static inline:


這樣改過之后蓄拣,就可以輸出正常的結(jié)果 (i:201)扬虚。因?yàn)檫@里定義的代碼是內(nèi)聯(lián)的 (inlined),所以它的效率和宏變量差不多球恤,但是可靠性比宏定義要好許多辜昵。再者,還可以設(shè)置斷點(diǎn)咽斧、類型檢查以及避免異常行為堪置。

基本上,宏的最佳使用場(chǎng)景是日志輸出张惹,可以使用__FILE__和__LINE__和 assert 宏舀锨。

詞法解析標(biāo)記

預(yù)處理完成以后,每一個(gè).m源文件里都有一堆的聲明和定義诵叁。這些代碼文本都會(huì)從 string 轉(zhuǎn)化成特殊的標(biāo)記流雁竞。

例如钦椭,下面是一段簡單的 Objective-C hello word 程序:


利用 clang 命令clang -Xclang -dump-tokens hello.m來將上面代碼的標(biāo)記流導(dǎo)出:


仔細(xì)觀察可以發(fā)現(xiàn)拧额,每一個(gè)標(biāo)記都包含了對(duì)應(yīng)的源碼內(nèi)容和其在源碼中的位置。注意這里的位置是宏展開之前的位置彪腔,這樣一來侥锦,如果編譯過程中遇到什么問題,clang 能夠在源碼中指出出錯(cuò)的具體位置德挣。

解析

接下來要說的東西比較有意思:之前生成的標(biāo)記流將會(huì)被解析成一棵抽象語法樹 (abstract syntax tree -- AST)恭垦。由于 Objective-C 是一門復(fù)雜的語言,因此解析的過程不簡單格嗅。解析過后番挺,源程序變成了一棵抽象語法樹:一棵代表源程序的樹。假設(shè)我們有一個(gè)程序hello.m:


當(dāng)我們執(zhí)行 clang 命令clang -Xclang -ast-dump -fsyntax-only hello.m之后屯掖,命令行中輸出的結(jié)果如下所示::


在抽象語法樹中的每個(gè)節(jié)點(diǎn)都標(biāo)注了其對(duì)應(yīng)源碼中的位置玄柏,同樣的,如果產(chǎn)生了什么問題贴铜,clang 可以定位到問題所在處的源碼位置粪摘。

延伸閱讀

clang AST 介紹

靜態(tài)分析

一旦編譯器把源碼生成了抽象語法樹,編譯器可以對(duì)這棵樹做分析處理绍坝,以找出代碼中的錯(cuò)誤徘意,比如類型檢查:即檢查程序中是否有類型錯(cuò)誤。例如:如果代碼中給某個(gè)對(duì)象發(fā)送了一個(gè)消息轩褐,編譯器會(huì)檢查這個(gè)對(duì)象是否實(shí)現(xiàn)了這個(gè)消息(函數(shù)椎咧、方法)。此外把介,clang 對(duì)整個(gè)程序還做了其它更高級(jí)的一些分析勤讽,以確保程序沒有錯(cuò)誤竹宋。

類型檢查

每當(dāng)開發(fā)人員編寫代碼的時(shí)候,clang 都會(huì)幫忙檢查錯(cuò)誤地技。其中最常見的就是檢查程序是否發(fā)送正確的消息給正確的對(duì)象蜈七,是否在正確的值上調(diào)用了正確的函數(shù)。如果你給一個(gè)單純的NSObject*對(duì)象發(fā)送了一個(gè)hello消息莫矗,那么 clang 就會(huì)報(bào)錯(cuò)飒硅。同樣,如果你創(chuàng)建了NSObject的一個(gè)子類Test, 如下所示:

@interfaceTest:NSObject@end

然后試圖給這個(gè)子類中某個(gè)屬性設(shè)置一個(gè)與其自身類型不相符的對(duì)象作谚,編譯器會(huì)給出一個(gè)可能使用不正確的警告三娩。

一般會(huì)把類型分為兩類:動(dòng)態(tài)的和靜態(tài)的。動(dòng)態(tài)的在運(yùn)行時(shí)做檢查妹懒,靜態(tài)的在編譯時(shí)做檢查雀监。以往,編寫代碼時(shí)可以向任意對(duì)象發(fā)送任何消息眨唬,在運(yùn)行時(shí)会前,才會(huì)檢查對(duì)象是否能夠響應(yīng)這些消息。由于只是在運(yùn)行時(shí)做此類檢查匾竿,所以叫做動(dòng)態(tài)類型瓦宜。

至于靜態(tài)類型,是在編譯時(shí)做檢查岭妖。當(dāng)在代碼中使用 ARC 時(shí)临庇,編譯器在編譯期間,會(huì)做許多的類型檢查:因?yàn)榫幾g器需要知道哪個(gè)對(duì)象該如何使用昵慌。例如假夺,如果 myObject 沒有 hello 方法,那么就不能寫如下這行代碼了:

[myObject hello]

其他分析

clang 在靜態(tài)分析階段斋攀,除了類型檢查外已卷,還會(huì)做許多其它一些分析。如果你把 clang 的代碼倉庫 clone 到本地蜻韭,然后進(jìn)入目錄lib/StaticAnalyzer/Checkers悼尾,你會(huì)看到所有靜態(tài)檢查內(nèi)容。比如ObjCUnusedIVarsChecker.cpp是用來檢查是否有定義了肖方,但是從未使用過的變量闺魏。而ObjCSelfInitChecker.cpp則是檢查在 你的初始化方法中中調(diào)用self之前,是否已經(jīng)調(diào)用[self initWith...]或[super init]了俯画。編譯器還進(jìn)行了一些其它的檢查析桥,例如在lib/Sema/SemaExprObjC.cpp的 2,534 行,有這樣一句:

Diag(SelLoc,diag::warn_arc_perform_selector_leaks);

這個(gè)會(huì)生成嚴(yán)重錯(cuò)誤的警告 “performSelector may cause a leak because its selector is unknown” 。

代碼生成

clang 完成代碼的標(biāo)記泡仗,解析和分析后埋虹,接著就會(huì)生成 LLVM 代碼。下面繼續(xù)看看hello.c:


要把這段代碼編譯成 LLVM 字節(jié)碼(絕大多數(shù)情況下是二進(jìn)制碼格式)娩怎,我們可以執(zhí)行下面的命令:

clang -O3-emit-LLVMhello.c-c-o hello.bc

接著用另一個(gè)命令來查看剛剛生成的二進(jìn)制文件:

llvm-dis < hello.bc | less

輸出如下:


在上面的代碼中搔课,可以看到main函數(shù)只有兩行代碼:一行輸出string,另一行返回0截亦。

再換一個(gè)程序爬泥,拿five.m為例,對(duì)其做相同的編譯崩瓤,然后執(zhí)行LLVM-dis < five.bc | less:


拋開其他的不說袍啡,單看main函數(shù):


上面代碼中最重要的是第 4 行,它創(chuàng)建了一個(gè)NSNumber對(duì)象却桶。第 7 行境输,給這個(gè) number 對(duì)象發(fā)送了一個(gè)description消息。第 8 行颖系,將description消息返回的內(nèi)容打印出來嗅剖。

優(yōu)化

要想了解 LLVM 的優(yōu)化內(nèi)容,以及 clang 能做哪些優(yōu)化集晚,我們先看一個(gè)略微復(fù)雜的 C 程序:這個(gè)函數(shù)主要是遞歸計(jì)算階乘:


先看看不做優(yōu)化的編譯情況窗悯,執(zhí)行下面命令:

clang -O0-emit-llvm factorial.c-c-o factorial.bc && llvm-dis < factorial.bc

重點(diǎn)看一下針對(duì)階乘部分生成的代碼:


看一下%9標(biāo)注的那一行区匣,這行代碼正是遞歸調(diào)用階乘函數(shù)本身偷拔,實(shí)際上這樣調(diào)用是非常低效的,因?yàn)槊看芜f歸調(diào)用都要重新壓棧亏钩。接下來可以看一下優(yōu)化后的效果莲绰,可以通過這樣的方式開啟優(yōu)化 -- 將-03標(biāo)志傳給 clang:

clang -O3-emit-llvm factorial.c-c-o factorial.bc && llvm-dis < factorial.bc

現(xiàn)在階乘計(jì)算相關(guān)代碼編譯后生成的代碼如下:


即便我們的函數(shù)并沒有按照尾遞歸的方式編寫,clang 仍然能對(duì)其做優(yōu)化處理姑丑,讓該函數(shù)編譯的結(jié)果中只包含一個(gè)循環(huán)蛤签。當(dāng)然 clang 能對(duì)代碼進(jìn)行的優(yōu)化還有很多方面树碱【J可以看以下這個(gè)比較不錯(cuò)的 gcc 的優(yōu)化例子ridiculousfish.com

延伸閱讀

LLVM blog: posts tagged 'optimization'

LLVM blog: vectorization improvements

LLVM blog: greedy register allocation

The Polly project

如何在實(shí)際中應(yīng)用這些特性

剛剛我們探討了編譯的全過程胰坟,從標(biāo)記到解析留拾,從抽象語法樹到分析檢查戳晌,再到匯編。讀者不禁要問痴柔,為什么要關(guān)注這些沦偎?

使用 libclan g或 clang 插件

之所以 clang 很酷:是因?yàn)樗且粋€(gè)開源的項(xiàng)目、并且它是一個(gè)非常好的工程:幾乎可以說全身是寶。使用者可以創(chuàng)建自己的 clang 版本豪嚎,針對(duì)自己的需求對(duì)其進(jìn)行改造搔驼。比如說,可以改變 clang 生成代碼的方式侈询,增加更強(qiáng)的類型檢查舌涨,或者按照自己的定義進(jìn)行代碼的檢查分析等等。要想達(dá)成以上的目標(biāo)扔字,有很多種方法泼菌,其中最簡單的就是使用一個(gè)名為libclang的C類庫。libclang 提供的 API 非常簡單啦租,可以對(duì) C 和 clang 做橋接哗伯,并可以用它對(duì)所有的源碼做分析處理。不過篷角,根據(jù)我的經(jīng)驗(yàn)焊刹,如果使用者的需求更高,那么 libclang 就不怎么行了恳蹲。針對(duì)這種情況虐块,推薦使用Clangkit,它是基于 clang 提供的功能嘉蕾,用 Objective-C 進(jìn)行封裝的一個(gè)庫贺奠。

最后,clang 還提供了一個(gè)直接使用 LibTooling 的 C++ 類庫错忱。這里要做的事兒比較多儡率,而且涉及到 C++,但是它能夠發(fā)揮 clang 的強(qiáng)大功能以清。用它你可以對(duì)源碼做任意類型的分析儿普,甚至重寫程序。如果你想要給 clang 添加一些自定義的分析掷倔、創(chuàng)建自己的重構(gòu)器 (refactorer)眉孩、或者需要基于現(xiàn)有代碼做出大量修改,甚至想要基于工程生成相關(guān)圖形或者文檔勒葱,那么 LibTooling 是很好的選擇浪汪。

自定義分析器

開發(fā)者可以按照Tutorial for building tools using LibTooling中的說明去構(gòu)造 LLVM ,clang 以及 clan g的附加工具凛虽。需要注意的是死遭,編譯代碼是需要花費(fèi)一些時(shí)間的,即時(shí)機(jī)器已經(jīng)很快了涩维,但是在編譯期間殃姓,我還是可以吃頓飯的袁波。

接下來,進(jìn)入到 LLVM 目錄蜗侈,然后執(zhí)行命令cd ~/llvm/tools/clang/tools/篷牌。在這個(gè)目錄中,可以創(chuàng)建自己獨(dú)立的 clang 工具踏幻。例如枷颊,我們創(chuàng)建一個(gè)小工具,用來檢查某個(gè)庫是否正確使用该面。首先將樣例工程克隆到本地夭苗,然后輸入make。這樣就會(huì)生成一個(gè)名為example的二進(jìn)制文件隔缀。

我們的使用場(chǎng)景是:假如有一個(gè)Observer類, 代碼如下所示:


接下來题造,我們想要檢查一下每當(dāng)這個(gè)類被調(diào)用的時(shí)候,在target對(duì)象中是否都有對(duì)應(yīng)的action方法存在猾瘸〗缗猓可以寫個(gè) C++ 函數(shù)來做這件事(注意,這是我第一次寫 C++ 程序牵触,可能不那么嚴(yán)謹(jǐn)):


上面的這個(gè)方法首先查找消息表達(dá)式淮悼, 以O(shè)bserver作為接收者,observerWithTarget:action:作為 selector揽思,然后檢查 target 中是否存在相應(yīng)的方法袜腥。雖然這個(gè)例子有點(diǎn)兒刻意,但如果你想要利用 AST 對(duì)自己的代碼庫做某些檢查钉汗,按照上面的例子來就可以了羹令。

clang的其他特性

clang還有許多其他的用途。比如儡湾,可以寫編譯器插件(例如特恬,類似上面的檢查器例子)并且動(dòng)態(tài)的加載到編譯器中。雖然我沒有親自實(shí)驗(yàn)過徐钠,但是我覺得在 Xcode 中應(yīng)該是可行的。再比如役首,也可以通過編寫 clang 插件來自定義代碼樣式(具體可以參見編譯過程)尝丐。

另外,如果想對(duì)現(xiàn)有的代碼做大規(guī)模的重構(gòu)衡奥, 而 Xcode 或 AppCode 本身集成的重構(gòu)工具無法達(dá)你的要求爹袁,你完全可以用 clang 自己寫個(gè)重構(gòu)工具。聽起來有點(diǎn)兒可怕矮固,讀讀下面的文檔和教程失息,你會(huì)發(fā)現(xiàn)其實(shí)沒那么難譬淳。

最后,如果是真的有這種需求盹兢,你完全可以引導(dǎo) Xcdoe 使用你自己編譯的 clang 邻梆。再一次,如果你去嘗試绎秒,其實(shí)這些事兒真的沒想象中那么復(fù)雜浦妄,反而會(huì)發(fā)現(xiàn)許多個(gè)中樂趣。

延伸閱讀

Clang Tutorial

X86_64 Assembly Language Tutorial

Custom clang Build with Xcode (I)(II)

Clang Tutorial (I),(II)(III)

Clang Plugin Tutorial

LLVM blog: What every C programmer should know (I),(II)(III)


翻譯作者:sunset

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末见芹,一起剝皮案震驚了整個(gè)濱河市剂娄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玄呛,老刑警劉巖阅懦,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異徘铝,居然都是意外死亡故黑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門庭砍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來场晶,“玉大人,你說我怎么就攤上這事怠缸∈幔” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵揭北,是天一觀的道長扳炬。 經(jīng)常有香客問我,道長搔体,這世上最難降的妖魔是什么恨樟? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮疚俱,結(jié)果婚禮上劝术,老公的妹妹穿的比我還像新娘。我一直安慰自己呆奕,他們只是感情好养晋,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梁钾,像睡著了一般绳泉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上姆泻,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天零酪,我揣著相機(jī)與錄音冒嫡,去河邊找鬼。 笑死四苇,一個(gè)胖子當(dāng)著我的面吹牛孝凌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛔琅,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼胎许,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了罗售?” 一聲冷哼從身側(cè)響起辜窑,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寨躁,沒想到半個(gè)月后穆碎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡职恳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年所禀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片放钦。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡色徘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出操禀,到底是詐尸還是另有隱情褂策,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布颓屑,位于F島的核電站斤寂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏揪惦。R本人自食惡果不足惜遍搞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望器腋。 院中可真熱鬧溪猿,春花似錦、人聲如沸蒂培。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽护戳。三九已至,卻和暖如春垂睬,著一層夾襖步出監(jiān)牢的瞬間媳荒,已是汗流浹背抗悍。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钳枕,地道東北人缴渊。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鱼炒,于是被迫代替她去往敵國和親衔沼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

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