Swift奇趣漫談A

Swift奇趣漫談

一历等、前言

這個(gè)系列的文章,是記錄自己在學(xué)習(xí)和使用Swift的過程中睁壁,遇到的一些奇怪或者有趣的知識(shí)點(diǎn),有些地方甚至和自己的直覺是相反的互捌。在這個(gè)過程中潘明,自己會(huì)結(jié)合匯編、SIL中間代碼秕噪,源碼等角度去探索钳降。

Swift是完全開源的語言,Swift主頁地址腌巾,主要采用C++編寫牲阁,Swift編譯過程如下:

Swift編譯過程

如上圖所示,Swift編譯流程是:
SwiftCode代碼 -> Swift AST語法樹 -> Raw Swift IL特有中間代碼 -> Canonical Swift IL特有簡潔中間代碼 -> LLVM IR中間代碼 -> Assembly匯編代碼 -> Executable二進(jìn)制

所以我們了解和學(xué)習(xí)Swift的方式有很多壤躲,比如閱讀源碼城菊,分析SIL中間語言、查看匯編代碼等碉克。比如新建一個(gè)main.swift凌唬,可以按照下面的指令,生成對應(yīng)的中間代碼:

# 生成語法樹
swiftc -dump-ast main.swift
# 生成最簡介的SIL代碼
swiftc -emit-sil main.swift
# 生成LLVM IR代碼
swiftc -emit-ir main.swift -o main.ll
# 生成匯編代碼
swiftc -emit-assembly main.swift -o main.s

二漏麦、匯編和調(diào)試

2.1 寄存器和內(nèi)存

通常客税,CPU會(huì)先將內(nèi)存中的數(shù)據(jù)存儲(chǔ)到寄存器中,然后再對寄存器中的數(shù)據(jù)進(jìn)行運(yùn)算撕贞。
假設(shè)內(nèi)存中有塊紅色空間的值是3更耻,現(xiàn)在想把它的值加1,并將結(jié)果存儲(chǔ)到藍(lán)色空間捏膨,匯編過程是:

匯編過程

對應(yīng)的匯編偽代碼是:

// 1.CPU會(huì)先將紅色內(nèi)存空間的值秧均,放到寄存器中:
movq 紅色內(nèi)存空間, %rax
// 2.然后讓 rax 寄存器和1相加
addq $0x1, %rax
// 3.最后將值賦值給內(nèi)存空間
movq %rax, 藍(lán)色內(nèi)存空間

2.2 匯編基礎(chǔ)

匯編有很多種類,常見的有 Intel匯編(Windows派系)号涯、AT&T匯編(Unix派系)目胡、ARM匯編等。
但是链快,在iOS開發(fā)中誉己,最主要的匯編語言是:

  • AT&T匯編 -> iOS模擬器 (M1芯片的電腦未知,猜測是ARM匯編)
  • ARM匯編 -> iOS真機(jī)設(shè)備

常見的匯編指令(注意:AT&T帶有操作數(shù)長度和%)

匯編指令

這里需要注意movqleaq的區(qū)別(類似值類型和引用類型的感覺):

# 將 rbp-0x18 指向內(nèi)存地址的值域蜗,取出來賦值給 rax
movq -0x18(%rbp),  %rax
# 將 rbp-0x18 指向內(nèi)存地址巨双,直接賦值給 rax
leaq -0x18(%rbp),  %rax

有16個(gè)常用的寄存器(加粗為常見的)

  • rax, rbx, rcx , rdx, rsi, rdi, rbp, rsp
  • r8, r9, r10, r11, r12, r13, r14, r15

寄存器的具體用途規(guī)律:

  • rax噪猾、rdx常作為函數(shù)的返回值使用
  • rdi、rsi筑累、rdx畏妖、rcx、r8疼阔、r9 等寄存器常作為函數(shù)的參數(shù)使用
  • rsp戒劫、rbp用于棧操作

rip作為指令指針:

  • 存儲(chǔ)著CPU下一條要執(zhí)行的指令的地址
  • 一旦CPU讀取一條指令、rip會(huì)自動(dòng)指向下一條指令(存儲(chǔ)下一條指令的地址)

小技巧:除外上面列出來的寄存器婆廊,還有很多其他寄存器迅细。其中r開頭的寄存器,都是64位寄存器(8字節(jié))淘邻,e開頭的寄存器都是32位寄存器(4字節(jié))茵典,ax、bx宾舅、cx開頭都是16位寄存器(2字節(jié))统阿,ah/al/bh/bl開頭都是8位寄存器(1個(gè)字節(jié))

Xcode打斷點(diǎn),進(jìn)入?yún)R編顯示頁面的方法:
Xcode菜單欄-->Debug-->Debug Workflow-->Always Show Disassembly

2.3 LLDB常用指令

LLDB是Xcode默認(rèn)的調(diào)試器筹我。

LLDB

舉例子來說:

register read rax  // 讀取rax寄存器地址
register read // 當(dāng)前可用的寄存器
x/3xw 0x00007fff816a7330  // 讀取內(nèi)存中的值
// 3組數(shù)據(jù) x:16進(jìn)制 w:4個(gè)字節(jié)為一組

2.4 LLDB斷點(diǎn)調(diào)試

LLDB

在真機(jī)開發(fā)過程中澜建,有個(gè)內(nèi)存地址規(guī)律:
內(nèi)存地址格式為:0x4bdc(%rip)屈糊,一般是全局變量贮尉、全局區(qū)(數(shù)據(jù)段)
內(nèi)存地址格式為:-0x78(%rbp)奕枝,一般是局部變量、棸逗唬空間
內(nèi)存地址格式為:0x10(%rax)麻献,一般是堆空間

三、匯編舉例

==3.1== 通過匯編猜扮,發(fā)現(xiàn)sizeof()的本質(zhì)是編譯器關(guān)鍵字勉吻,而不是函數(shù)。

int main(int argc, const char * argv[]) {
  int c = sizeof(int);
  int b = 5;  // 此處打斷點(diǎn)
  return 0;
}

打斷點(diǎn)進(jìn)入?yún)R編指令的截圖:

sizeof

==3.2== Swift里面inout的運(yùn)行匯編截圖:

var a = 5
func test(_ number: inout Int) {
  number = 8
}
test(&a)  // 斷點(diǎn)處

運(yùn)行旅赢,進(jìn)入?yún)R編指令頁面:

inout

繼續(xù)跟進(jìn)匯編指令齿桃,進(jìn)入callq里面:

inout

如果,去掉inout鲜漩,將函數(shù)改造之后源譬,再對比匯編指令:

var a = 5
func test(_ number: Int) {
}
test(a)  // 斷點(diǎn)處
inout

mov指令直接將a的值,賦值給了形參孕似。lea指令將a的內(nèi)存地址傳遞到了函數(shù)內(nèi)部。說明了inout的工作原理刮刑。

==3.3== Swift是有默認(rèn)的main函數(shù)的喉祭,只是不用自己去寫养渴,當(dāng)我們進(jìn)入斷點(diǎn)之后,能看到匯編頁面的頂部有main方法的入口:

DemoSwift`main:  // 進(jìn)入?yún)R編泛烙,看到main調(diào)用
  0x100003f00 <+0>: pushq %rbp
  0x100003f01 <+1>: movq  %rsp, %rbp

==3.4== 所有的函數(shù)(方法)不管有沒有返回值理卑,編譯后,都會(huì)有return的蔽氨,這也是call指令和jump指令的關(guān)鍵區(qū)別藐唠,代碼演示:

func testFunc() {
  let a = 5
  let b = 6
  let c = a + b
}
testFunc()
let a = 5  // 斷點(diǎn)位置
return
  • 從另外的角度,函數(shù)運(yùn)行之后鹉究,需要回到調(diào)用函數(shù)的位置宇立,所以必然有ret指令。
  • call指令是調(diào)用函數(shù)自赔,必然會(huì)有ret指令回來繼續(xù)執(zhí)行妈嘹。
  • jump是直接跳到對應(yīng)的指令執(zhí)行,不會(huì)再回來绍妨。

四润脸、Swift奇趣漫談

開始步入正題...

4.1 Swift的函數(shù)重載

1.返回值類型 和 函數(shù)重載無關(guān),下面三個(gè)函數(shù)他去,同時(shí)存在毙驯,是可以編譯通過的。

func sum(v1: Int, v2: Int) -> Int { 1 }
func sum(v1: Int, v2: Int) -> Double { 1.0 }
func sum(v1: Int, v2: Int) { }

奇怪點(diǎn)來了??:雖然編譯通過了灾测,但是如果調(diào)用尔苦,就會(huì)報(bào)錯(cuò):

func sum(v1: Int, v2: Int) -> Int { 1 }
func sum(v1: Int, v2: Int) -> Double { 1.0 }
func sum(v1: Int, v2: Int) { }

// ?報(bào)錯(cuò):Ambiguous use of 'sum(v1:v2:)'
sum(v1: 10, v2: 20)

4.2 函數(shù)重載和默認(rèn)參數(shù)

當(dāng)函數(shù)帶有默認(rèn)參數(shù)值, 和函數(shù)重載一起使用時(shí)行施,會(huì)有奇怪的地方允坚,代碼:

func sum(v1: Int, v2: Int) -> Int { v1 + v2 }
func sum(v1: Int, v2: Int, v3: Int = 10) -> Int { v1 + v2 + v3 }
// 會(huì)調(diào)用誰? 結(jié)果是什么蛾号?
print( sum(v1: 10, v2: 20) )

奇怪點(diǎn):首先稠项,上面是編譯通過的,但是從調(diào)用角度來說鲜结,2個(gè)函數(shù)都是對的展运,不過結(jié)果卻是30,調(diào)用了第一個(gè)函數(shù)精刷。對于開發(fā)者而言拗胜,其實(shí)是有歧義的。

4.3 省略參數(shù)標(biāo)簽和可變參數(shù)

當(dāng)省略參數(shù)標(biāo)簽和可變參數(shù)一起出現(xiàn)時(shí)怒允,會(huì)調(diào)用誰呢埂软?代碼:

func sum(_ v1: Int, _ v2: Int) -> Int {
  print("調(diào)用了 -- sum1")
  return v1 + v2
}
func sum(_ numbers: Int...) -> Int {
  print("調(diào)用了 -- sum2")
  var total = 0
  for item in numbers {
    total += item
  }
  return total
}
print( sum(10, 20) )

上面的代碼,從調(diào)用側(cè)來看纫事,調(diào)用上下兩個(gè)函數(shù)都是正確的勘畔,但是結(jié)果是調(diào)用了 -- sum1所灸,那么這是為什么呢?

4.4 函數(shù)重載總結(jié)

當(dāng)我們把這幾個(gè)函數(shù)放到一起炫七,也是能編譯通過的爬立,但是最后調(diào)用了誰呢?這個(gè)讀者可以自己去試試:

func sum(_ numbers: Int...) -> Int {
    print("sum2")
    var total = 0
    for item in numbers {
        total += item
    }
    return total
}

func sum(_ v1: Int, _ v2: Int) -> Int {
    print("sum1")
    return v1 + v2
}

func sum(_ v1: Int, _ v2: Int, _ v3: Int = 10) -> Int {
    print("sum3")
    return v1 + v2
}

print( sum(10, 20) )  // 你猜會(huì)調(diào)用哪個(gè)函數(shù)万哪?

解釋:關(guān)于Swift的函數(shù)重載侠驯,Swift內(nèi)部到底怎么選擇執(zhí)行哪個(gè)函數(shù)的?Swift在執(zhí)行重載解析時(shí)奕巍,類型檢查器會(huì)對每個(gè)重載的函數(shù)吟策,進(jìn)行打分,找到一個(gè)分?jǐn)?shù)最高的來執(zhí)行伍绳,至于打分的標(biāo)準(zhǔn)踊挠,在下面的源碼里,規(guī)則有很多冲杀,比如:非泛型的得分高于泛型的效床。

源碼地址:
https://github.com/apple/swift/blob/main/lib/Sema/CSRanking.cpp

需要注意的是:Swift的這種帶有歧義的函數(shù)重載,在C++里面是通不過編譯的权谁。

4.5 Swift內(nèi)聯(lián)函數(shù)

如果開啟了編譯器優(yōu)化(Release模式默認(rèn)會(huì)開啟優(yōu)化)剩檀,編譯器會(huì)自動(dòng)將某些函數(shù)變成內(nèi)聯(lián)函數(shù).

  • Inline Function 其實(shí)就是將函數(shù)體調(diào)用展開成函數(shù)體

但是,即使開啟了編譯器優(yōu)化旺芽,有些函數(shù)還是不會(huì)被內(nèi)聯(lián):

  • 函數(shù)體比較長沪猴,比如超過500行
  • 包含遞歸調(diào)用(尾遞歸除外)
  • 包含動(dòng)態(tài)派發(fā)

主動(dòng)開啟編譯器優(yōu)化:optimization

optimization

關(guān)于函數(shù)內(nèi)聯(lián)的2個(gè)關(guān)鍵字:

// 永遠(yuǎn)不會(huì)被內(nèi)聯(lián),即使開啟了編譯器優(yōu)化
@inline(never) func test1() {}
// 開啟編譯器優(yōu)化后采章,即使代碼很長运嗜,也會(huì)被內(nèi)聯(lián)(遞歸調(diào)用、動(dòng)態(tài)派發(fā)的函數(shù)除外)
@inline(__always) func test2() {}

4.6 枚舉的初始化

Swift的枚舉是值類型悯舟,struct也是值類型担租,那么二者有哪些區(qū)別呢?比如下面的代碼:

enum MyEnum {
    case test1(Int, Int, Int)
    case test2
}

struct MyStruct {
    let age: Int
}

let e = MyEnum.test1(2, 3, 4)  // 斷點(diǎn)處
let p = MyStruct(age: 5)

運(yùn)行進(jìn)入斷點(diǎn)抵怎,在匯編指令頁面奋救,我發(fā)現(xiàn)了神奇的對比:

Enum

枚舉enum沒有初始化方法,而結(jié)構(gòu)體struct是有初始化方法的反惕。也就是我們創(chuàng)建enum變量時(shí)尝艘,系統(tǒng)識(shí)別到是枚舉類型后,開啟內(nèi)存姿染,直接開始mov指令初始化內(nèi)存背亥。而struct是需要call指令調(diào)用init初始化方法的。

關(guān)于識(shí)別Metadata的相關(guān)內(nèi)容,可以看看下面兩篇文章:

4.7 枚舉關(guān)聯(lián)值和原始值的內(nèi)存區(qū)別

枚舉關(guān)聯(lián)值(Associated Values):枚舉的成員值和其他類型的值隘梨,關(guān)聯(lián)存儲(chǔ)在一起:

enum Date {
  case digit(yeaer: Int, month: Int, day: Int)
  case desc(String)
}
var d1 = Date.digit(yeaer: 2021, month: 10, day: 1)
d1 = .desc("2021-10-01")

// 利用case模式程癌,可以獲得關(guān)聯(lián)值舷嗡,比如當(dāng)是`Date.digit`類型轴猎,拿到y(tǒng)ear
if case let .digit(yeaer: year, month: _, day: _) = d1 {
    print(year)
}

枚舉原始值(Raw Values):枚舉成員可以使用相同類型的默認(rèn)值預(yù)先對應(yīng),這個(gè)默認(rèn)值叫做:原始值进萄,注意捻脖,如果原始值類型是String、Int中鼠,Swift會(huì)自動(dòng)分配原始值可婶。

enum Grade: String {
  case perfect = "A"
  case great = "B"
  case good = "C"
}
print(Grade.great)
print(Grade.perfect.rawValue)  // rawValue獲取原始值

枚舉關(guān)聯(lián)值和原始值的內(nèi)存對比:

// 1.關(guān)聯(lián)值
enum Password {
  case number(Int, Int, Int, Int)
  case other
}

var pwd = Password.number(1, 2, 3, 4)
pwd = .other

print( MemoryLayout.size(ofValue: pwd) ) // 33 實(shí)際用到的內(nèi)存空間大小
print( MemoryLayout.stride(ofValue: pwd) ) // 40 系統(tǒng)分配占用的空間大小,會(huì)內(nèi)存對齊
print( MemoryLayout.alignment(ofValue: pwd) ) // 8 內(nèi)存對齊參數(shù)

print("---------------")

// 2.原始值
enum Names: Int {
  case xiaoMing = 2, xiaoHong = 5, xiaoHou = 6, XiaoLi = 9
}

let name = Names.XiaoLi

print( MemoryLayout.size(ofValue: name) ) // 1 實(shí)際用到的內(nèi)存空間大小
print( MemoryLayout.stride(ofValue: name) ) // 1 系統(tǒng)分配占用的空間大小援雇,會(huì)內(nèi)存對齊
print( MemoryLayout.alignment(ofValue: name) ) // 1 內(nèi)存對齊參數(shù)

==結(jié)論:==
原始值 是固定死的矛渴,可以通過代碼switch判斷返回,所以不用占存儲(chǔ)內(nèi)存惫搏。
關(guān)聯(lián)值 是要要外面?zhèn)魅氲木呶拢菚?huì)變化的,必須得存起來筐赔,是會(huì)占用內(nèi)存的铣猩。

  • 1個(gè)字節(jié)存儲(chǔ)成員值
  • N個(gè)字節(jié)存儲(chǔ)關(guān)聯(lián)值(N取占用內(nèi)存最大的關(guān)聯(lián)值),任何一個(gè)case的關(guān)聯(lián)值都共用這N個(gè)字節(jié)

我們可以查看內(nèi)存茴丰,來驗(yàn)證我們的結(jié)論达皿,使用Xcode的Debug->Debug Workflow->View Member工具,來查看內(nèi)存:

/// 獲得變量的內(nèi)存地址
func memPtr<T>(ofValue v: inout T) -> UnsafeRawPointer {
    if MemoryLayout.size(ofValue: v) == 0 {
        return UnsafeRawPointer(bitPattern: 0x1)!
    }
    return withUnsafePointer(to: &v) { UnsafeRawPointer($0) }
}

enum TestEnum {
    case test1, test2, test3
}
var t = TestEnum.test1
print(memPtr(ofValue: &t))  // 打印內(nèi)存地址贿肩,復(fù)制粘貼到`View Member`中查看內(nèi)存
print("--------我是分割線峦椰,為了方便斷點(diǎn)--------")

t = .test2
print("--------我是分割線,為了方便斷點(diǎn)--------")

t = .test3
print("--------我是分割線汰规,為了方便斷點(diǎn)--------")
member

相應(yīng)的汤功,我們使用相同的辦法,驗(yàn)證關(guān)聯(lián)值類型枚舉的內(nèi)存:

enum TestEnum {
    case test1(Int, Int, Int)
    case test2(Int, Int)
}

var t = TestEnum.test1(5, 6, 7)
print(memPtr(ofValue: &t))
print("--------我是分割線控轿,為了方便斷點(diǎn)--------")

t = .test2(8, 9)
print("--------我是分割線冤竹,為了方便斷點(diǎn)--------")
member

注意,因?yàn)槟M器CPU是小端模式茬射,所以內(nèi)存是從低到高的倒著布局

4.8 ??和if-let配合使用

這算是小技巧鹦蠕,在一些第三方庫中,能看到下面代碼的寫法在抛,當(dāng)想同時(shí)判斷2個(gè)Optional時(shí)钟病,下面的寫法比較簡潔:

let a: Int? = 1
let b: Int? = 2
let c = a ?? b ?? 3  // c是Int, 1

let a: Int? = nil
let b: Int? = 2
if let c = a ?? b { // 類似于 if a != nil || b != nil
  print(c)
}
if let _ = a ?? b { } // 或者這種奇怪的代碼

==結(jié)論:== ??返回值類的類型,就看 最后一個(gè)??后面的變量類型,和后一個(gè)變量類型一致肠阱。

4.9 多重可選項(xiàng)

觀察代碼如下:

let num1: Int? = 10
let num2: Int?? = num1 // 注意票唆,這里的??不是空合并運(yùn)算符
let num3: Int?? = 10

問題:num2和num3的區(qū)別是什么?比如print(num2 == num3)結(jié)果是什么屹徘?
代碼修改之后:

let num1: Int? = nil
let num2: Int?? = num1 // 注意走趋,這里的??不是空合并運(yùn)算符
let num3: Int?? = nil

// 下面分別輸出什么?
print(num2 == num3)
print( (num2 ?? 1) ?? 2 )
print( (num3 ?? 1) ?? 2 )

對于上面的問題噪伊,我們需要理解Optional的本質(zhì)簿煌,我們都知道Optional的本質(zhì)就是枚舉:

public enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}

所以,我們可以通過使用LLDB指令frame variable -R或者fr v -R查看區(qū)別(后者只是前者的縮寫)鉴吹。打上斷點(diǎn)姨伟,在控制臺(tái),可以查看指令的介紹:

help frame
help frame variable

運(yùn)行打斷點(diǎn)的截圖:

Optional

如果使用盒子的方式去理解豆励,就是下面的圖夺荒,注意盒子顏色的含義:

Optional
Optional

4.10 結(jié)構(gòu)體初始化器的下面2種寫法,是否一樣良蒸?

struct Point {
  var x: Int = 5
  var y: Int = 6
}

struct Point {
  var x: Int
  var y: Int

  init() {  // 寫出來了init()
    x = 5
    y = 6
  }
}

結(jié)論:二者寫法完全等價(jià)技扼。可以通過匯編指令查看诚啃,二者匯編指令是完全一致的淮摔。

4.11 Swift類的初始化過程

class Person {
  let age: Int
  init(age: Int) { self.age = age }
}

let p = Person(age: 18)  // 斷點(diǎn)

通過進(jìn)入?yún)R編指令頁面, 一步一步跟進(jìn)始赎,發(fā)現(xiàn)一個(gè)Swift類初始化和橙,必然經(jīng)過下面的步驟:

  • 1.Class.init()
  • 2.Class.__allocating_init()
  • 3.libswiftCore.dylib: swift_allocObject
  • 4.libswiftCore.dylib: swift_slowAlloc
  • 5.libsystem_malloc.dylib: malloc

在Mac、iOS中的malloc函數(shù)分配的內(nèi)存大小造垛,總是16的倍數(shù)魔招。同時(shí),不管是Swift還是OC五辽,初始化類办斑,在堆空間開辟內(nèi)存,最終都是調(diào)用的malloc函數(shù)杆逗。

malloc

4.12 Swift的超類

下面代碼分別輸出什么乡翅?

class Person {}
class Student: Person {}

print( class_getInstanceSize(Person.self) ) // 16
print( class_getSuperclass(Student.self)! ) // Persong
print( class_getSuperclass(Person.self)! ) // _TtCs12_SwiftObject

結(jié)論:純Swift類,是有個(gè)共同的隱藏的基類:Swift.SwiftObject
這個(gè)Swift源碼里也可以得到證明:
https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h

完...等攢夠一波罪郊,再輸出第二篇...

END蠕蚜。
我是小侯爺。
在帝都艱苦奮斗悔橄,白天是上班族靶累,晚上是知識(shí)服務(wù)工作者腺毫。
如果讀完覺得有收獲的話,記得關(guān)注和點(diǎn)贊哦挣柬。
非要打賞的話潮酒,我也是不會(huì)拒絕的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邪蛔,一起剝皮案震驚了整個(gè)濱河市急黎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌店溢,老刑警劉巖叁熔,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件委乌,死亡現(xiàn)場離奇詭異床牧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)遭贸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門戈咳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壕吹,你說我怎么就攤上這事著蛙。” “怎么了耳贬?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵踏堡,是天一觀的道長。 經(jīng)常有香客問我咒劲,道長顷蟆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任腐魂,我火速辦了婚禮帐偎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛔屹。我一直安慰自己削樊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布兔毒。 她就那樣靜靜地躺著漫贞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪育叁。 梳的紋絲不亂的頭發(fā)上迅脐,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音擂红,去河邊找鬼仪际。 笑死围小,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的树碱。 我是一名探鬼主播肯适,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼成榜!你這毒婦竟也來了框舔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赎婚,失蹤者是張志新(化名)和其女友劉穎刘绣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挣输,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纬凤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撩嚼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片停士。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖完丽,靈堂內(nèi)的尸體忽然破棺而出恋技,到底是詐尸還是另有隱情,我是刑警寧澤逻族,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布蜻底,位于F島的核電站,受9級(jí)特大地震影響聘鳞,放射性物質(zhì)發(fā)生泄漏薄辅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一搁痛、第九天 我趴在偏房一處隱蔽的房頂上張望长搀。 院中可真熱鬧,春花似錦鸡典、人聲如沸源请。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谁尸。三九已至,卻和暖如春纽甘,著一層夾襖步出監(jiān)牢的瞬間良蛮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工悍赢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留决瞳,地道東北人货徙。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像皮胡,于是被迫代替她去往敵國和親痴颊。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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