理解 Clang Module 和 Module Map 語(yǔ)法

Clang Module 是大概 2013 年左右出現(xiàn)的揭芍,它的出現(xiàn)是為了解決傳統(tǒng)基于 C 語(yǔ)言的編程語(yǔ)言的頭文件包含的弊端馒索。也是現(xiàn)代 Apple 平臺(tái)軟件開發(fā)一定會(huì)用到的一個(gè)技術(shù)萍悴,了解 Clang Module 對(duì)我們組織代碼結(jié)構(gòu)翔始,理解 Xcode 編譯流程彪置,優(yōu)化編譯速度斩箫,定位編譯錯(cuò)誤等都會(huì)有幫助吏砂。

傳統(tǒng)頭文件包含的弊端

傳統(tǒng)的頭文件包含,存在以下幾個(gè)主要的問(wèn)題:

編譯性能問(wèn)題

對(duì)于傳統(tǒng)頭文件包含乘客,預(yù)處理器會(huì)將頭文件的內(nèi)容復(fù)制粘貼過(guò)來(lái)替換 #include 預(yù)處理指令狐血。而很多頭文件內(nèi)都會(huì)包含相同的其它頭文件,例如底層依賴易核,系統(tǒng)庫(kù)等匈织,這樣造成了不同的源文件中會(huì)出現(xiàn)重復(fù)的內(nèi)容。也就是說(shuō)牡直,對(duì)于 M 個(gè)源文件缀匕,如果有 N 個(gè)頭文件,復(fù)雜度量級(jí)是 M x N 的碰逸,編譯器會(huì)對(duì)每個(gè)重復(fù)的內(nèi)容都進(jìn)行一次文本分析乡小,做了大量重復(fù)的工作,拖慢了編譯時(shí)間饵史。

例如系統(tǒng)的 Foundation 框架满钟,框架內(nèi)嵌套包含了 800 個(gè)以上的其它頭文件胜榔,整個(gè)框架的大小在 9MB 以上,作為最最基礎(chǔ)的框架湃番,幾乎每個(gè)源文件都會(huì)包含 Foundation.h夭织。對(duì)于傳統(tǒng)的頭文件包含,F(xiàn)oundation.h 的內(nèi)容及其包含的其它頭文件會(huì)被不斷地重復(fù)進(jìn)行詞法分析語(yǔ)義分析吠撮,拖慢編譯速度尊惰。

脆弱性

脆弱性是因?yàn)?#include 替換得到的內(nèi)容會(huì)受到其它預(yù)處理指令的影響。例如頭文件中有某個(gè)符號(hào) XXX泥兰,如果在包含這個(gè)頭文件之前弄屡,存在類似 #define XXX "other text" 這樣的宏定義,就會(huì)導(dǎo)致頭文件中的所有 XXX 都被替換為 "other text"逾条,而導(dǎo)致編譯錯(cuò)誤琢岩。

#define XXX "other text"
#include "XXX.h"

必須使用慣例方案來(lái)解決一些問(wèn)題

傳統(tǒng)的頭文件包含無(wú)法解決頭文件重復(fù)包含的問(wèn)題,因此大家都用一種慣例來(lái)避免重復(fù)包含师脂。

#ifndef __XXXX_H__
#define __XXXX_H__
// 頭文件內(nèi)容
#endif

雖然現(xiàn)代開發(fā)工具都能自動(dòng)生成這些担孔,但還是存在一定的不便。

另外為了解決宏在多個(gè)庫(kù)之間重名的問(wèn)題吃警,大家都會(huì)把宏的名稱起的很長(zhǎng)很長(zhǎng)糕篇,增加前綴和后綴。

對(duì)工具的迷惑性

在 C 語(yǔ)言為基礎(chǔ)的語(yǔ)言中,軟件庫(kù)的邊界不是很清晰,比如很難辨別一個(gè)頭文件到底是哪個(gè)語(yǔ)言的镣煮,因?yàn)?C、C++墩崩、Objective-C 等語(yǔ)言的頭文件都是 .h。也很難弄清楚一個(gè)頭文件到底是屬于哪個(gè)庫(kù)的侯勉。這對(duì)于開發(fā)基于這些軟件庫(kù)的工具帶來(lái)了一定的難度鹦筹。

Clang Module 能解決什么問(wèn)題?

語(yǔ)義導(dǎo)入

Clang Module 從傳統(tǒng)頭文件包含的文本導(dǎo)入改進(jìn)成了更健壯址貌,效率更高的語(yǔ)義導(dǎo)入铐拐。當(dāng)編譯器看到一個(gè) Module 導(dǎo)入指令時(shí),編譯器會(huì)去加載一個(gè)二進(jìn)制文件练对,這個(gè)二進(jìn)制文件提供了這個(gè)模塊所有 API 的信息遍蟋,這些 API 可以直接給其它代碼使用。

編譯性能提升

Clang Module 提升了編譯性能螟凭,每個(gè)模塊只需要編譯一次虚青,然后會(huì)生成一個(gè)模塊的二進(jìn)制表示(.pcm,預(yù)編譯模塊螺男,下文會(huì)說(shuō)明)挟憔,并緩存到磁盤上钟些。下次遇到 import 這個(gè)模塊時(shí)烟号,編譯器不需要再次編譯 Module绊谭,而是直接讀取這個(gè)緩存的二進(jìn)制表示即可。

上下文無(wú)關(guān)

Clang Module 解決了脆弱性的問(wèn)題汪拥,每個(gè) Module 都是一個(gè)獨(dú)立的實(shí)體达传,會(huì)被隔離地、獨(dú)立的編譯迫筑,是上下文無(wú)關(guān)的宪赶。當(dāng) import 模塊時(shí),會(huì)忽略 import 上下文的其它的預(yù)處理指令脯燃,這樣 import 之前的預(yù)處理指令不會(huì)對(duì)模塊導(dǎo)入產(chǎn)生任何影響搂妻。

每個(gè)模塊都是一個(gè)自包含的個(gè)體,他們上下文無(wú)關(guān)辕棚,相互隔離欲主,因此不在需要使用一些慣例方法來(lái)避免出現(xiàn)一些問(wèn)題,因?yàn)檫@些問(wèn)題已經(jīng)不會(huì)出現(xiàn)了逝嚎。

自己制作一個(gè)模塊

為了能對(duì)有一個(gè)直觀的了解扁瓢,我們可以自己動(dòng)手制作一個(gè) 模塊。用 Xcode 創(chuàng)建一個(gè)新的 iOS app 工程作為測(cè)試使用补君。然后在工程根目錄下新建一個(gè) group 命名為 Frameworks引几。

在命令行中進(jìn)入 Frameworks 文件夾,新建一個(gè) Dog.framework 文件夾挽铁,名字可以隨意伟桅,這里是隨便起的。

mkdir Dog.framework

然后回到 Xcode 中叽掘,在 Frameworks 目錄上右擊鼠標(biāo)楣铁,選擇 Adds files to ... 把 Dog.framework 添加到 Frameworks 目錄內(nèi)。
此時(shí)編譯會(huì)報(bào)錯(cuò) Framework not found Dog够掠,接下來(lái)我們看看怎樣制作出一個(gè) Xcode 能正確識(shí)別并編譯的模塊民褂。

在 Dog.framework 中新建 Dog.swift 文件并添加以下內(nèi)容:

// Dog.swift
import Foundation

public class Dog: NSObject {
    public func bark() {
        print("bark")
    }

    @objc func objcBark() {
        print("objc bark")
    }
}

接下來(lái)我們來(lái)為這個(gè) framework 生成接口文件。在命令行中執(zhí)行以下命令:

swiftc -module-name Dog -c Dog.swift -target arm64-apple-ios16.2-simulator -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator16.2.sdk -emit-module -emit-objc-header -emit-objc-header-path Dog-Swift.h

swiftc 是 Swift 語(yǔ)言的編譯器疯潭,它底層也調(diào)用了 clang赊堪。下面對(duì)參數(shù)一一進(jìn)行說(shuō)明:

  • -module-name Dog 模塊的名稱,使用者可以通過(guò) import + 這個(gè)名稱來(lái)引入模塊竖哩。
  • -c Dog.swift 指定要編譯的源文件哭廉。
  • -target arm64-apple-ios16.2-simulator 指定生成目標(biāo)的架構(gòu)。
  • -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator16.2.sdk 指定要鏈接進(jìn)來(lái)的 SDK相叁,這里使用的是 iOS 16.2 的模擬器版本遵绰。
  • -emit-module 會(huì)生成一個(gè) .swiftdoc 文件和一個(gè) .swiftmodule 文件辽幌。
  • -emit-objc-header 生成 Objective-C 頭文件,僅包含被標(biāo)記為 @objc 的符號(hào)椿访。
  • -emit-objc-header-path 指定 Objective-C 頭文件的路徑乌企,這里我們遵循了 Xcode 的慣例,使用 ”模塊名+Swift.h“ 來(lái)命名成玫。

雖然需要的文件已經(jīng)生成了加酵,但是并不是 Xcode 支持的 module 目錄結(jié)構(gòu),無(wú)法被 Xcode 讀取哭当。我們可以通過(guò)觀察 Xcode 創(chuàng)建的 Framework 來(lái)了解這種結(jié)構(gòu)猪腕,來(lái)創(chuàng)建正確的結(jié)構(gòu)。

在 Dog.framework 文件夾中創(chuàng)建 Headers 文件夾钦勘,然后把 Dog-Swift.h 移動(dòng)到 Headers 文件夾中陋葡。然后在 Dog.framework 文件夾中再創(chuàng)建一個(gè) Modules 文件夾,然后在 Modules 文件夾中創(chuàng)建 Dog.swiftmodule 文件夾彻采,把 Dog.swiftdoc 和 Dog.swiftmodule 移動(dòng)到 Dog.swiftmodule 文件夾中腐缤。最后把這兩個(gè)文件重命名為 arm64.swiftdoc 和 arm64.swiftmodule。

當(dāng)前 Dog.framework 的目錄結(jié)構(gòu)為:

Dog.framework/
|---- Dog
|---- Headers
|    |---- Dog-Swift.h
|---- Modules
     |---- Dog.swiftmodule
         |---- arm64.swiftdoc
         |---- arm64.swiftmodule 

現(xiàn)在接口已經(jīng)有了颊亮,但是還沒有二進(jìn)制庫(kù)文件柴梆,依然無(wú)法編譯通過(guò),下面我們來(lái)生成二進(jìn)制庫(kù)文件终惑。

執(zhí)行以下命令:

swiftc -module-name Dog -parse-as-library -c Dog.swift -target arm64-apple-ios16.2-simulator -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator16.2.sdk -emit-object

這個(gè)命令中有很多參數(shù)跟上一個(gè)命令是一樣的绍在,在此不做重復(fù)說(shuō)明,僅說(shuō)明下上個(gè)命令中沒有的參數(shù)雹有。

  • -parse-as-library 讓編譯器把文件解釋為一個(gè)庫(kù)偿渡,而不是一個(gè)可執(zhí)行文件。
  • -emit-object 輸出目標(biāo)文件霸奕。

這個(gè)命令執(zhí)行完以后會(huì)生成 Dog.o 這個(gè)目標(biāo)文件溜宽,然后我們需要把目標(biāo)文件歸檔為庫(kù)。

libtool -static Dog.o -arch_only arm64 -o Dog

這里為了簡(jiǎn)化流程選擇了創(chuàng)建靜態(tài)庫(kù)质帅,而不是動(dòng)態(tài)鏈接庫(kù)适揉,因此使用 -static。這時(shí)在 Dog.framework 中會(huì)出現(xiàn) Dog 這個(gè)二進(jìn)制靜態(tài)庫(kù)文件煤惩。此時(shí)我們?cè)?ViewController 中 import Dog 然后編譯工程嫉嘀,就能編譯通過(guò)了。說(shuō)明當(dāng)前這種目錄結(jié)構(gòu)可以讓 Xcode 正確找到模塊所需文件魄揉。

Module map

接下來(lái)我們來(lái)試一試用 Objective-C 來(lái)調(diào)用 Dog 模塊會(huì)怎樣剪侮。

在上面的工程中再創(chuàng)建一個(gè) Objective-C 類,命名為 OCObject洛退,并讓 Xcode 自動(dòng)創(chuàng)建頭文件橋接文件瓣俯,并添加以下代碼:

// OCObject.h
@interface OCObject : NSObject

- (void)doSomething;

@end
// OCObject.m
#import "OCObject.h"
#import <Dog/Dog-Swift.h>

@implementation OCObject

- (void)doSomething {
    Dog *dog = [[Dog alloc] init];
    [dog objcBark];
}

@end

會(huì)發(fā)現(xiàn)此時(shí)是可以打印出 objc mark 的杰标。然后把 #import <Dog/Dog-Swift.h> 替換成標(biāo)準(zhǔn)的模塊導(dǎo)入語(yǔ)法 @import Dog;,編譯卻報(bào)錯(cuò)了彩匕,提示 ”Module 'Dog' not found“腔剂。

這時(shí)因?yàn)?framework 中缺少了一個(gè)重要的 modulemap 文件,Xcode 就無(wú)法找到模塊推掸。#import <Dog/Dog-Swift.h> 之所以有效是因?yàn)樗旧硎且粋€(gè)向前兼容的語(yǔ)句桶蝎,如果 framework 支持模塊,則導(dǎo)入模塊谅畅,如果 framework 不支持模塊,它會(huì)像 #include 一樣去搜索路徑中找到這個(gè)頭文件噪服,直接把文本內(nèi)容粘貼到這里毡泻。

Module map 指明了 framework 中的頭文件邏輯結(jié)構(gòu)應(yīng)該如何映射為模塊。參考用 Xcode 創(chuàng)建 framework 時(shí)自動(dòng)創(chuàng)建的 module map 文件粘优,會(huì)發(fā)現(xiàn)在 Modules 文件夾下有一個(gè) module.modulemap 文件仇味,其內(nèi)容如下:

framework module ObserveModuleStructure {
  umbrella header "ObserveModuleStructure.h"

  export *
  module * { export * }
}

module ObserveModuleStructure.Swift {
  header "ObserveModuleStructure-Swift.h"
  requires objc
}

通過(guò)參考 clang 的文檔,來(lái)對(duì)這個(gè)語(yǔ)法一一進(jìn)行說(shuō)明:

  • framework module XXXX 定義了一個(gè) framework 語(yǔ)義的模塊
  • umbrella header "XXXX.h" 說(shuō)明把 XXXX.h 文件作為模塊的 unbrella header雹顺,傘頭文件相當(dāng)于模塊中所有公共頭文件的一個(gè)集合丹墨,方便使用者導(dǎo)入。
  • export * 將所有子模塊中的符號(hào)進(jìn)行重導(dǎo)出到主模塊中
  • module * { export * } 定義子模塊嬉愧,這里為 * 則是為 umbrella header 中的每個(gè)頭文件都創(chuàng)建一個(gè)子模塊贩挣。

根據(jù)這個(gè)語(yǔ)法編寫自己的 module map 文件,路徑為 Dog.framework/Modules/module.modulemap:

// Dog.framework/Modules/module.modulemap
framework module Dog {
    umbrella header "Dog.h"
    export *
    module * { export * }
}

module Dog.Swift {
    header "Dog-Swift.h"
    requires objc
}

此時(shí)依然編譯報(bào)錯(cuò)没酣,還需要一個(gè) unbrella header 文件王财,創(chuàng)建一個(gè) Dog.h 文件放到 Dog.framework/Headers/ 中,內(nèi)容為空即可裕便。然后就可以編譯通過(guò)绒净,打印出 bark objc。

Module Map 語(yǔ)言語(yǔ)法

官方把這種語(yǔ)法叫做模塊映射語(yǔ)言(Module Map Language)偿衰。

根據(jù) Clang 的文檔挂疆,模塊映射語(yǔ)言在 Clang 的大版本之間可能不會(huì)保持穩(wěn)定,因此在平常的開發(fā)中下翎,讓 Xcode 去自動(dòng)生成就好缤言。

模塊聲明

[framework] module module-id [extern_c] [system] {
    module-member
}

framework

framework 代表這個(gè)模塊是是一個(gè) Darwin 風(fēng)格的 framework。Darwin 風(fēng)格的 framework 主要出現(xiàn)在 macOS 和 iOS 操作系統(tǒng)中漏设,它的全部?jī)?nèi)容都包含在一個(gè) Name.framework 文件夾中墨闲,這個(gè) Name 就是 framework 的名字,這個(gè)文件夾的內(nèi)容布局如下:

Name.framework/
    Modules/module.modulemap    framework 的模塊映射
    Headers/                    包含了 framework 中的頭文件
    PrivateHeaders/             包含了 framework 中私有的頭文件
    Frameworks/                 包含嵌入的其它 framework
    Resources/                  包含額外的資源
    Name                        指向共享庫(kù)的符號(hào)鏈接

system

system 指定了這個(gè)模塊是一個(gè)系統(tǒng)模塊郑口。當(dāng)一個(gè)系統(tǒng)模塊被重編譯后鸳碧,模塊的所有頭文件都會(huì)被當(dāng)做系統(tǒng)頭文件盾鳞,這樣一些警告就不會(huì)出現(xiàn)。這和在頭文件中放置 #pragma GCC system_header 等效瞻离。

extern_c

extern_c 指明了模塊中包含的 C 代碼可以被 C++ 使用腾仅。當(dāng)這個(gè)模塊被編譯用來(lái)給 C++ 調(diào)用時(shí),所有模塊中的頭文件都會(huì)被包含在一個(gè)隱含的 extern "C" 代碼塊中套利。

模塊體

模塊體包含了 header推励、requires 等常見的聲明和子模塊聲明,例如:

framework module Dog {
    umbrella header "Dog.h"
    requires objc
    module * { export * }
}

header

header 指定了要把哪些頭文件映射為模塊肉迫。umbrella header 則是指定了綜合性傘頭文件验辞。

requires

requires 聲明指定了導(dǎo)入這個(gè)模塊的編譯單元需要滿足的條件。這個(gè)條件有語(yǔ)言喊衫、平臺(tái)跌造、編譯環(huán)境和目標(biāo)特定功能等。例如 requires cplusplus11 表示模塊需要在支持 C++11 的環(huán)境中使用族购,requires objc 表示模塊需要在支持 Objective-C 語(yǔ)言的環(huán)境中使用壳贪。

module

module 用來(lái)聲明模塊中的子模塊,如果是 module * 則代表模塊中的每個(gè)頭文件都會(huì)作為一個(gè)子模塊寝杖。

子模塊聲明

在主模塊的模塊體中嵌套地聲明模塊就是子模塊违施。例如在 MyLib 模塊中聲明一個(gè)子模塊 A,寫法如下:

module MyLib {
    module A {
        header "A.h"
        export *
    }
}

explicit

explicit 修飾符是用來(lái)修飾子模塊的瑟幕。如果想使用被 explicit 修飾的子模塊磕蒲,必須在 import 時(shí)指定子模塊的名字,像這樣 import modulename.submodulename收苏,或者這個(gè)子模塊已經(jīng)被其它已導(dǎo)入的模塊重導(dǎo)出過(guò)亿卤。

export

export 指定了將哪個(gè)模塊的 API 進(jìn)行重新導(dǎo)出,成為 export 所在的模塊的 API鹿霸。

export_as

export_as 將當(dāng)前模塊的 API 通過(guò)另一個(gè)指定的模塊導(dǎo)出排吴。

module MyFrameworkCore {
    export_as MyFramework
}

上面的例子中,MyFrameworkCore 中的 API 將通過(guò) MyFramework 導(dǎo)出懦鼠。

模塊映射語(yǔ)言還包含很多其它的聲明語(yǔ)句钻哩,例如 useconfig_macrs肛冶、link街氢、conflict 等,由于在 iOS 開發(fā)中出現(xiàn)的不是很多睦袖,這里就不做一一說(shuō)明珊肃,有興趣可以查看 Clang 的官方文檔。

Clang Module 的緩存機(jī)制

Clang 可以通過(guò)讀取 modulemap 文件的內(nèi)容將 modulemap 中指定的模塊編譯成預(yù)編譯模塊(Precompiled Module),后綴名是 .pcm伦乔。

clang -cc1 -emit-obj use.c -fmodules -fimplicit-module-maps -fmodules-cache-path=prebuilt -fdisable-module-hash

上面的命令通過(guò)指定參數(shù) implicit-module-maps 讓編譯器根據(jù)一定的規(guī)則自己去查找 modulemap 文件厉亏,通過(guò)指定參數(shù) modules-cache-path 告訴編譯器預(yù)編譯模塊的緩存路徑。Clang 會(huì)根據(jù) modulemap 中的信息編譯各個(gè)模塊烈和,將生成的 .pcm 文件放到 prebuilt 目錄下爱只。

.pcm 文件以一種編譯器可以輕松讀取并解析的格式保存了模塊的信息,之后編譯器在編譯其它模塊時(shí)如果遇到了需要依賴這個(gè)模塊招刹,則可以快速的從 .pcm 中讀取模塊信息而不需要重新編譯模塊恬试。

在 Xcode 中使用 Clang Module

用 Xcode 創(chuàng)建的框架或庫(kù)都是默認(rèn)開啟 Clang Module 支持的,也就是在 Build Settings 中疯暑,Defines Module 的設(shè)置為 YES训柴。如果是很老的庫(kù)可能沒有開啟,手動(dòng)把 Defines Module 設(shè)置為 YES 即可缰儿。

當(dāng) Defines Modules 是 YES 是畦粮,Xcode 在編譯工程時(shí)會(huì)給 clang 命令增加 -fmodules 等模塊相關(guān)參數(shù),開啟模塊支持乖阵。

結(jié)語(yǔ)

很多時(shí)候,開發(fā)工具都對(duì)我們隱藏了很多底層的細(xì)節(jié)预麸,了解這些細(xì)節(jié)瞪浸,可以幫助我們了解底層的原理,分析并解決一些棘手的問(wèn)題吏祸。Clang 是 Apple 平臺(tái)上重要的工具对蒲,值得我們?nèi)パ芯刻剿鳌8兄x您的閱讀贡翘,如果文章有不正確的地方蹈矮,或者您有自己的見解,歡迎發(fā)表評(píng)論來(lái)討論鸣驱。

參考資料

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

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