golang中defer的執(zhí)行過程是怎樣的麸拄?

在同一個goroutine中:

多個defer的調(diào)用棧原理是什么实柠?
defer函數(shù)是如何調(diào)用的?

為了探究其中的奧秘我準備了如下代碼:

package main
import "fmt"

func main() {
    xx()
}
func xx() {
    defer aaa(100, "hello aaa")
    defer bbb("hello bbb")
    return
}

func aaa(x int, arg string) {
    fmt.Println(x, arg)
}

func bbb(arg string) {
    fmt.Println(arg)
}

輸出:
bbb
100 hello aaa
從輸出結果看很像棧的數(shù)據(jù)結構特性:后進先出(LIFO)橱脸。

首先從匯編入手去查看xx()函數(shù)的執(zhí)行過程抒和,命令如下:
go tool compile -S main.go >> main.s

"".xx STEXT size=198 args=0x0 locals=0x30
    0x0000 00000 (main.go:9)    TEXT    "".xx(SB), ABIInternal, $48-0
    0x0000 00000 (main.go:9)    MOVQ    (TLS), CX
    0x0009 00009 (main.go:9)    CMPQ    SP, 16(CX)
    0x000d 00013 (main.go:9)    JLS 188
    0x0013 00019 (main.go:9)    SUBQ    $48, SP
    0x0017 00023 (main.go:9)    MOVQ    BP, 40(SP)
    0x001c 00028 (main.go:9)    LEAQ    40(SP), BP
    0x0021 00033 (main.go:9)    FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0021 00033 (main.go:9)    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0021 00033 (main.go:9)    FUNCDATA    $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x0021 00033 (main.go:10)   PCDATA  $2, $0
    0x0021 00033 (main.go:10)   PCDATA  $0, $0
    0x0021 00033 (main.go:10)   MOVL    $24, (SP)
    0x0028 00040 (main.go:10)   PCDATA  $2, $1
    0x0028 00040 (main.go:10)   LEAQ    "".aaa·f(SB), AX
    0x002f 00047 (main.go:10)   PCDATA  $2, $0
    0x002f 00047 (main.go:10)   MOVQ    AX, 8(SP)
    0x0034 00052 (main.go:10)   MOVQ    $100, 16(SP)
    0x003d 00061 (main.go:10)   PCDATA  $2, $1
    0x003d 00061 (main.go:10)   LEAQ    go.string."hello aaa"(SB), AX
    0x0044 00068 (main.go:10)   PCDATA  $2, $0
    0x0044 00068 (main.go:10)   MOVQ    AX, 24(SP)
    0x0049 00073 (main.go:10)   MOVQ    $9, 32(SP)
    0x0052 00082 (main.go:10)   CALL    runtime.deferproc(SB)
    0x0057 00087 (main.go:10)   TESTL   AX, AX
    0x0059 00089 (main.go:10)   JNE 172
    0x005b 00091 (main.go:11)   MOVL    $16, (SP)
    0x0062 00098 (main.go:11)   PCDATA  $2, $1
    0x0062 00098 (main.go:11)   LEAQ    "".bbb·f(SB), AX
    0x0069 00105 (main.go:11)   PCDATA  $2, $0
    0x0069 00105 (main.go:11)   MOVQ    AX, 8(SP)
    0x006e 00110 (main.go:11)   PCDATA  $2, $1
    0x006e 00110 (main.go:11)   LEAQ    go.string."hello bbb"(SB), AX
    0x0075 00117 (main.go:11)   PCDATA  $2, $0
    0x0075 00117 (main.go:11)   MOVQ    AX, 16(SP)
    0x007a 00122 (main.go:11)   MOVQ    $9, 24(SP)
    0x0083 00131 (main.go:11)   CALL    runtime.deferproc(SB)
    0x0088 00136 (main.go:11)   TESTL   AX, AX
    0x008a 00138 (main.go:11)   JNE 156
    0x008c 00140 (main.go:12)   XCHGL   AX, AX
    0x008d 00141 (main.go:12)   CALL    runtime.deferreturn(SB)

發(fā)現(xiàn)aaa()函數(shù)的參數(shù)及調(diào)用函數(shù)deferproc(SB):


 0x0021 00033 (main.go:10)   MOVL    $24, (SP)
 0x0028 00040 (main.go:10)   PCDATA  $2, $1 
 0x0028 00040 (main.go:10)   LEAQ    "".aaa·f(SB), AX
 0x002f 00047 (main.go:10)   PCDATA  $2, $0 
 0x002f 00047 (main.go:10)   MOVQ    AX, 8(SP)
 0x0034 00052 (main.go:10)   MOVQ    $100, 16(SP)
 0x003d 00061 (main.go:10)   PCDATA  $2, $1 
 0x003d 00061 (main.go:10)   LEAQ    go.string."hello aaa"(SB), AX
 0x0044 00068 (main.go:10)   PCDATA  $2, $0 
 0x0044 00068 (main.go:10)   MOVQ    AX, 24(SP)
 0x0049 00073 (main.go:10)   MOVQ    $9, 32(SP)
 0x0052 00082 (main.go:10)   CALL    runtime.deferproc(SB)

下面重點代碼的統(tǒng)一說明:

//1, (SP) 將24放入棧頂(24其實是下面所說的deferd函數(shù)參數(shù)類型的長度和)膳沽。
 0x0021 00033 (main.go:10)   MOVL    $24, (SP)

//2, 8(SP) 將aaa函數(shù)指針放入AX;將aaa函數(shù)指針放入到8(SP)中汗菜。
 0x0028 00040 (main.go:10)   LEAQ    "".aaa·f(SB), AX
 0x002f 00047 (main.go:10)   MOVQ    AX, 8(SP)

//3, 16(SP)把函數(shù)aaa第一個參數(shù)100放入到16(SP)中让禀。
 0x0034 00052 (main.go:10)   MOVQ    $100, 16(SP)

//4, 24(SP)獲取第二個參數(shù)的內(nèi)存地址并賦值給AX;AX中值賦值給24(SP)。
 0x003d 00061 (main.go:10)   LEAQ    go.string."hello aaa"(SB), AX
 0x0044 00068 (main.go:10)   MOVQ    AX, 24(SP)

//5,32(SP),將第二個參數(shù)字符串長度9賦值到32(SP)中陨界。
 0x0049 00073 (main.go:10)   MOVQ    $9, 32(SP)

//調(diào)用runtime.deferproc(SB)
 0x0052 00082 (main.go:10)   CALL    runtime.deferproc(SB)

0(SP) = 24 //aaa(int, string)參數(shù)類型長度和
8(SP) = &aaa(int, string)//deferd函數(shù)指針
16(SP) = 100// 第一個參數(shù)值100
24(SP) = "hello aaa"http://第二個參數(shù)
32(SP) = 9//第二個參數(shù)字符串長度
從以上2部分匯編代碼可以看出巡揍,函數(shù)相關數(shù)據(jù)放到了SP中且連續(xù)。2菌瘪,發(fā)現(xiàn)
defer aaa(int, string)編譯器會插入deferproc(SB)函數(shù)腮敌。
去看一下源碼:

//runtime/panic.go

func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
    if getg().m.curg != getg() {
        throw("defer on system stack")
    }
    sp := getcallersp()
    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)
    callerpc := getcallerpc()

    d := newdefer(siz)
    if d._panic != nil {
        throw("deferproc: d.panic != nil after newdefer")
    }
    d.fn = fn
    d.pc = callerpc
    d.sp = sp
    switch siz {
    case 0:
        // Do nothing.
    case sys.PtrSize:
        *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp))
    default:
        memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz))
    }
    return0()
}
deferproc(siz int32, fn *funcval)

發(fā)現(xiàn)這個函數(shù)的參數(shù)是int32,*funcval俏扩。它們兩個代表什么糜工?我們有gdb去跟蹤一下具體什么意思:


圖1

siz=0x18就是說siz=24。而aaa(int, string)的參數(shù)int占8個字節(jié)录淡,string占16個字節(jié)捌木。為什么string類型占16個字節(jié)?
因為string類型的原型是:

type stringStruct struct {
    str unsafe.Pointer
    len int
}

unsafe.Pointer占8個字節(jié)嫉戚,int占8個字節(jié)刨裆。
具體字符串講解可以看我以前的文章golang中的string、編碼
接下來看*funcval:它的原型如下:

//runtime/runtime2.go

type funcval struct {
   fn uintptr
   // variable-size, fn-specific data here
}

funcval是個struct彬檀,里面的成員是個fn uintptr帆啃,根據(jù)fn字面意思猜測是函數(shù)的指針。

前文已經(jīng)說過bbb(int, string)函數(shù)的相關數(shù)據(jù)放到了SP中凤覆,那func deferproc(siz int32, fn * funcval) 中的參數(shù)就是運行時系統(tǒng)會從sp中拿取siz和*fn然后調(diào)用deferproc(siz int32, fn * funcval)链瓦。

我們用gdb看一下這里面fn指向的函數(shù)到底是什么:


圖2

原來d.fn.fn就是aaa(int, string)函數(shù)的具體指令拆魏。
那d代表什么呢盯桦,跟蹤發(fā)現(xiàn):

d := newdefer(siz)

去看一下它的原型:

func newdefer(siz int32) *_defer

它的返回值是*_defer,看一下它的定義:

//runtime/runtime2.go

type _defer struct {
    siz     int32 
    started bool
    sp      uintptr // sp at time of defer
    pc      uintptr
    fn      *funcval 
    _panic  *_panic // panic that is running defer
    link    *_defer
}

它是個結構體渤刃。我們先查看siz拥峦,fn,link這3個參數(shù)就好卖子,其他參數(shù)由于篇幅有限下文講解略号。
siz:deferd函數(shù)參數(shù)原型字節(jié)長度的和。
fn:deferd函數(shù)指針洋闽。
link: 是什么意思玄柠??诫舅?羽利??刊懈?
帶著問題去看一下newdefer(siz)的實現(xiàn):

func newdefer(siz int32) *_defer {
    var d *_defer
    sc := deferclass(uintptr(siz))
    // 當前goroutine的g結構體對象  
    gp := getg()
    if sc < uintptr(len(p{}.deferpool)) {
                //當前goroutine綁定的p
        pp := gp.m.p.ptr()
                
        if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil {
            // Take the slow path on the system stack so
            // we don't grow newdefer's stack.
            systemstack(func() {//切換到系統(tǒng)棧
                lock(&sched.deferlock)
                //從全局deferpool拿一些defer放到p的本地deferpool
                for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil {
                    d := sched.deferpool[sc]
                    sched.deferpool[sc] = d.link
                    d.link = nil
                    pp.deferpool[sc] = append(pp.deferpool[sc], d)
                }
                unlock(&sched.deferlock)
            })
        }
        if n := len(pp.deferpool[sc]); n > 0 {
            d = pp.deferpool[sc][n-1]
            pp.deferpool[sc][n-1] = nil
            pp.deferpool[sc] = pp.deferpool[sc][:n-1]
        }
    }
    if d == nil {//緩存中沒有創(chuàng)建defer
        // Allocate new defer+args.
        systemstack(func() {
            total := roundupsize(totaldefersize(uintptr(siz)))
            d = (*_defer)(mallocgc(total, deferType, true))
        })
        if debugCachedWork {
            // Duplicate the tail below so if there's a
            // crash in checkPut we can tell if d was just
            // allocated or came from the pool.
            d.siz = siz
            d.link = gp._defer
            gp._defer = d
            return d
        }
    }
    d.siz = siz //賦值siz
        //將g的_defer賦值給d.link
    d.link = gp._defer
        //d賦值給g._defer
    gp._defer = d
    
    return d
}

以上是defer生成過程这弧,大體意思就是先從緩存中找defer如果沒有就創(chuàng)建一個娃闲,然后將size,link進行賦值匾浪。
重點看如下代碼:

    d.link = gp._defer
    gp._defer = d

以上2行代碼實現(xiàn)中已經(jīng)有解釋皇帮,這里再詳細解釋一下:
這2句的意思是,將剛剛生成的defer綁定到g._defer上蛋辈,就是將最新的defer放到
g._defer上作為鏈表頭属拾。然后將g._defer綁定到d.link上,見下方示意圖:

[當前的g]{_defer} => [新的d1]{link} => [g]{老的_defer}

如果再有新生成的defer(d2)則鏈表如下:

[當前的g]{_defer} => [新的d2]{link} => [新的d1]{link} => [g]{老的_defer}

回到deferproc(siz int32, fn *funcval)函數(shù)中來,newdefer(siz)上面第二行是什么意思呢梯浪?:

    argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn)

繼續(xù)用gdb跟蹤一下捌年,發(fā)現(xiàn)涉及到argp的在這一行,見下方截圖2:


圖2

發(fā)現(xiàn)了memmove函數(shù),它的作用是拷貝挂洛。就是將argp位置為起點拷貝siz(這里為24個字節(jié))字節(jié)到d結構體后后面礼预。
運行這行看一下復制到d結構體后面的數(shù)據(jù)是什么?見圖3:

圖3

圖3中紅框中的第一行是0x64 它的10進制表示為100虏劲。證明這個是aaa函數(shù)的第一個參數(shù)托酸,同理第二行0x4b9621為第二個參數(shù)字符串的指針,去看一下是否為預想的那樣柒巫,見圖4:

圖4

上圖為10進制表示方便ascii中查找對應的字符励堡,從ascii表中可知確實為aaa函數(shù)的第二個參數(shù)hello aaa。從而我得出結論deferd函數(shù)的參數(shù)是在deferd結構體后面堡掏。第三行代表字符串長度应结。也就是說第二行和第三行代表了字符串原型(結構體)的值。

繼續(xù)跟蹤函數(shù)執(zhí)行過程:

 defer bbb("hello bbb")

bbb(string)的執(zhí)行過程和上面aaa(int, string)函數(shù)執(zhí)行過程是一樣的泉唁,這里不再重復演示鹅龄。
deferproc棧執(zhí)行完之后運行return處,見圖5:


圖5

然后按s進入return實現(xiàn)處(到了deferreturn棧)亭畜,見下圖6:


圖6

去看一下它的實現(xiàn):

//rutime/painc.go

//go:nosplit
func deferreturn(arg0 uintptr) {
    gp := getg() //獲取當前的g 
    d := gp._defer //獲取當前g的_defer鏈表頭

  //d為什么可以為nil,因為defer函數(shù)可以嵌套例如:
  //  defer a -> defer b -> defer c
  //deferreturn函數(shù)被調(diào)用至少一次扮休,就是將鏈表里的defer都執(zhí)行完就直接返回了。
    if d == nil {
        return
    }
    sp := getcallersp()
    if d.sp != sp {
        return
    }

    //將deferd函數(shù)參數(shù)復制到arg0處拴鸵,為調(diào)用deferd函數(shù)做準備玷坠。
    switch d.siz {
    case 0:
        // Do nothing.
    case sys.PtrSize://如果siz的大小為指針大小直接如下復制,目的是減少cpu運算劲藐。
        *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
    default:
        memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
    }

    fn := d.fn //將d.fn拷貝一份
    d.fn = nil //將d.fn設置為空
    gp._defer = d.link//將當前defer的下一個defer綁定到鏈表頭八堡。
    freedefer(d) //將d釋放掉
       //fn為deferd函數(shù),第二個參數(shù)為deferd函數(shù)的參數(shù)
    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}
fn := d.fn
d.fn = nil 
gp._defer = d.link
freedefer(d) 

重點解釋一下上面4行代碼:將鏈表下一個defer綁定到gp._defer處聘芜。將當前的defer釋放掉兄渺。見下方示意圖:

[當前的g]{_defer} => [新的d2]{link} => [新的d1]{link} => [g]{老的_defer}

運行完d2:

[當前的g]{_defer} => [新的d1]{link} => [g]{老的_defer}

然后看一下下方jmpdefer函數(shù):

    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))

這個函數(shù)是具體執(zhí)行defer函數(shù)地方,我們看它實現(xiàn)之前先記住下圖圖7的deferreturn入口地址厉膀,下面會說到這個地址溶耘。


圖7

jmpdefer函數(shù)實現(xiàn)見下方代碼:

TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
    MOVQ    fv+0(FP), DX    // fn
    MOVQ    argp+8(FP), BX  // caller sp
    LEAQ    -8(BX), SP  // caller sp after CALL
    MOVQ    -8(SP), BP  // restore BP as if deferreturn returned (harmless if framepointers not in use)
    SUBQ    $5, (SP)    // return to CALL again
    MOVQ    0(DX), BX
    JMP BX  // but first run the deferred function

一行一行解釋:

    MOVQ    fv+0(FP), DX    // fn

將函數(shù)第一個參數(shù)fn指針復制給DX,從而后續(xù)代碼可以從DX中取fn的指針來執(zhí)行deferd函數(shù)二拐。

    MOVQ    argp+8(FP), BX  // caller sp

將函數(shù)第二個參數(shù)argp指針復制給BX,這個指針是deferd函數(shù)第一個參數(shù)地址凳兵。

    LEAQ    -8(BX), SP  // caller sp after CALL

從上面第2條指令可知BX存放的是deferd函數(shù)第一個參數(shù)地址百新。因為此時gbd調(diào)試的是bbb(string)這個函數(shù),所以此時的參數(shù)是個字符串結構體庐扫,總共占16個字節(jié)饭望,前8個字節(jié)是數(shù)據(jù)指針,后8個是長度形庭。那-8(BX)里面又是什么數(shù)據(jù)呢铅辞,就是說bbb(string)參數(shù)值前面(低位)是什么東東。用gdb跟一下執(zhí)行完這條指令看一下SP(因為賦值給了SP)中內(nèi)存的值是啥,見圖8萨醒。


圖8

第一行就是我們要確定的-8(BX)
第二行是bbb(string)中參數(shù)斟珊,它是字符串結構體中字符串指針,指向具體的字符串富纸。
第三行是字符串的長度囤踩,這里為9。
我們看一下棧的情況見圖9:


圖9

0x4872c6是什么晓褪,指針堵漱?試著去看一下它是否能指向具體內(nèi)存見下圖10

圖10

原來是main.xx+145地址處的call runtime.deferreturn指令。還記得剛才的圖7嗎涣仿,我再截一下圖7勤庐,見圖11:
圖11

紅線處下一行就是0x4872c6與圖10是一樣的值。根據(jù)圖11好港,這個地址是rutime.deferreturn(SB)的下一個指令愉镰,就是說這個地址是rutime.deferreturn(SB)返回地址。
仔細觀察這兩個地址:

0x4872c1 == rutime.deferreturn(SB)
0x4872c6 == rutime.deferreturn(SB)的下一個指令地址(也叫返回地址)

發(fā)現(xiàn)他們相差5個字節(jié)媚狰。根據(jù)匯編知識可知岛杀,cpu是如何找到下一個指令的呢阔拳,是通過當前指令所占字節(jié)數(shù)所確定的崭孤。
len(0x4872c6) - len(0x4872c1) == 5 可知

call runtime.deferreturn(sb)

占5個字節(jié),所以0x4872c1+5就可得到下一個指令首地址糊肠。

第4行:

    MOVQ    -8(SP), BP  // restore BP as if deferreturn returned (harmless if framepointers not in use)

打印BP的值=0xc000032778
看一下棧的情況辨宠,見圖12


圖12

當前的棧已經(jīng)是main.xx了。

第5行:

SUBQ  $5, (SP)  # return to CALL again

從第3行中的解釋可知货裹,如果SP所指向的數(shù)據(jù)(runtime.deferreturn返回地址)減5的話嗤形,正好是runtime.deferreturn(SB)的指令入口。見圖13:

圖13

第6弧圆,7行:

MOVQ    0(DX), BX
JMP BX  // but first run the deferred function

將DX所指向的函數(shù)指令賦值給BX
執(zhí)行fn.fn也就是bbb(string)赋兵。
執(zhí)行到bbb(string)處笔咽,見圖14

圖14

此時的rsp向低地址移動了0x70個字節(jié)。
將bbb(string)末尾打上斷點并執(zhí)行到那里見圖15:
圖15

圖14中SP向低地址移動了0x70霹期。
圖15中SP向高地址移動了0x70叶组。
就是SP會恢復到之前的指向狀態(tài)。之前的SP指向哪里呢历造?就是圖13演示中的runtime.deferreturn(SB)入口處甩十。
在看圖15 add rsp, 0x70指令下一行是個ret指令。這個在bbb(string)函數(shù)是沒有的吭产,是編譯器添加上去的侣监,目的是pop當前棧頂?shù)?個字節(jié)到rip寄存器中,這樣cpu執(zhí)行rip里的指令就會執(zhí)行到runtime.deferreturn(SB)里從而實現(xiàn)了類似遞歸的調(diào)用deferreturn(SB)的作用臣淤。這樣就依次可以把deferd鏈上的執(zhí)行完橄霉。

繼續(xù)到runtime.deferreturn(SB)中
如下代碼:

    if d == nil {
        return
    }

這個個if語句就是判斷defer鏈上是否還有deferd函數(shù),如果沒有就直接返回了邑蒋。從而避免無限遞歸循環(huán)下去酪劫。
里面還有幾句代碼:

sp := getcallersp()
    if d.sp != sp {
        return
    }

有興趣的小伙伴可以去試著看一下這里為什么這么寫,由于時間有限這段代碼的研究就不在這里展開了寺董。

這篇文章主要是講解defer的執(zhí)行過程覆糟,由于篇幅原因,我把panic遮咖、recover滩字、還有容易出錯的defer語句的探究在下一篇中講解,敬請期待~

參考
defer ---go語言核心編程技術
golang中的defer

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末御吞,一起剝皮案震驚了整個濱河市麦箍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陶珠,老刑警劉巖挟裂,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揍诽,居然都是意外死亡诀蓉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門暑脆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渠啤,“玉大人,你說我怎么就攤上這事添吗×げ埽” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妓美。 經(jīng)常有香客問我僵腺,道長,這世上最難降的妖魔是什么壶栋? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任想邦,我火速辦了婚禮,結果婚禮上委刘,老公的妹妹穿的比我還像新娘丧没。我一直安慰自己,他們只是感情好锡移,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布呕童。 她就那樣靜靜地躺著,像睡著了一般淆珊。 火紅的嫁衣襯著肌膚如雪夺饲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天施符,我揣著相機與錄音往声,去河邊找鬼。 笑死戳吝,一個胖子當著我的面吹牛浩销,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播听哭,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼慢洋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了陆盘?” 一聲冷哼從身側(cè)響起普筹,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隘马,沒想到半個月后太防,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡酸员,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年蜒车,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沸呐。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡醇王,死狀恐怖呢燥,靈堂內(nèi)的尸體忽然破棺而出崭添,到底是詐尸還是另有隱情,我是刑警寧澤叛氨,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布呼渣,位于F島的核電站棘伴,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屁置。R本人自食惡果不足惜焊夸,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蓝角。 院中可真熱鬧阱穗,春花似錦、人聲如沸使鹅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽患朱。三九已至鲁僚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裁厅,已是汗流浹背冰沙。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留执虹,地道東北人拓挥。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像袋励,于是被迫代替她去往敵國和親撞叽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355