Golang 中 defer 機制分析

引言

在 Go 中 defer 常用于資源的釋放赏殃,會在函數(shù)返回之前調(diào)用,經(jīng)常被用于關(guān)閉文件描述符间涵、關(guān)閉數(shù)據(jù)庫連接以及解鎖資源仁热。下面就深入 Go 語言源碼介紹 defer 關(guān)鍵字的實現(xiàn)。


目錄

  1. defer 關(guān)鍵字
  2. defer 源碼分析
  3. 總結(jié)
  4. 了解更多


1勾哩、defer 關(guān)鍵字

在 Go 中 defer 語句是一般用來做一些清理或者后置的工作抗蠢。defer 語句的執(zhí)行順序是 LIFO 規(guī)則。當(dāng) defer 與 return 一起使用時有啥需要注意的思劳,看下面的代碼迅矛,思考一下相關(guān)輸出的值。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("anonymousVarReturn return value is", anonymousVarReturn())
    fmt.Println("anonymousVarReturn2 return value is", anonymousVarReturn2(1))
    fmt.Println("anonymousVarReturn3 return value is", anonymousVarReturn3(1))
    fmt.Println("namedVarReturn4 return value is", namedVarReturn4())
}

func anonymousVarReturn() int {
    var i = 0
    defer func() {
        i++
        fmt.Println("anonymousVarReturn defer, i is ", i)
    }()
    return 0
}

func anonymousVarReturn2(i int) int {
    defer func() {
        i++
        fmt.Println("anonymousVarReturn2 defer, i is", i)
    }()
    return i
}

func anonymousVarReturn3(i int) int {
    defer func(i int) {
        i++
        fmt.Println("anonymousVarReturn3 defer, i is", i)
    }(i)
    return i
}

func namedVarReturn4() (i int) {
    defer func(i *int) {
        *i++
        fmt.Println("namedVarReturn4 defer, i is", *i)
    }(&i)
    return 1
}

展開看下面相關(guān)值輸出潜叛,命名的返回值被 defer 修改了秽褒,是不是和你想的有點不一樣?

anonymousVarReturn defer, i is  1
anonymousVarReturn return value is 0
anonymousVarReturn2 defer, i is 2
anonymousVarReturn2 return value is 1
anonymousVarReturn3 defer, i is 2
anonymousVarReturn3 return value is 1
namedVarReturn4 defer, i is 2
namedVarReturn4 return value is 2

帶著疑惑查找了一番文檔威兜,在 go 的 Defer, Panic, and Recover 博客里發(fā)現(xiàn)了相關(guān)說明销斟。

The behavior of defer statements is straightforward and predictable. There are three simple rules:

1. A deferred function's arguments are evaluated when the defer statement is evaluated.
2. Deferred function calls are executed in Last In First Out order after the surrounding function returns.
3. Deferred functions may read and assign to the returning function's named return values.

規(guī)則 3 就可以說明了上面代碼的返回問題了,defer 方法可能會讀取和賦值給返回的命名變量椒舵,所以 defer 方法能在 return 返回之后繼續(xù)操作同一返回變量蚂踊。
看上面的方法 anonymousVarReturn、anonymousVarReturn2笔宿、anonymousVarReturn3 和 namedVarReturn4犁钟,可以發(fā)現(xiàn)匿名的返回值是沒有被 defer 方法修改的,因為匿名的方法返回變量名是 Go 自動創(chuàng)建的泼橘,在 defer 里面就不會操作到該變量涝动。

2、defer 源碼分析

分析源碼之前需要了解 Go 的匯編相關(guān)知識侥加,要想看懂匯編內(nèi)容捧存,需要把看完下面相關(guān)鏈接的文檔,看完會對匯編有個更深的了解担败,再看下面的匯編代碼就不會那么迷糊了昔穴。

我們來簡單分析一下命名返回變量的方法的相關(guān)匯編代碼。

   1 package main
   2 
   3 import (
   4    "fmt"
   5 )
   6 
   7 func main() {
   8    fmt.Println("namedVarReturn4 return value is", namedVarReturn4())
   9 }
  10 
  11 func namedVarReturn4() (i int) {
  12    defer func(i *int) {
  13        *i++
  14        fmt.Println("namedVarReturn4 defer, i is", *i)
  15    }(&i)
  16    return 1
  17 }

本機 Go 版本是 go1.16.5 darwin/amd64提前,使用 go tool compile 工具輸出相關(guān)匯編代碼吗货,先看 help 輸出相關(guān)命令參數(shù)。


go tool compile  --help
usage: compile [options] file.go...
  -N    disable optimizations
  -S    print assembly listing
  -l    disable inlining
  …… 省略部分

運行下面命令輸出上面代碼的匯編代碼狈网。


# 輸出整個代碼的匯編指令
go tool compile -N -l -S main.go

# go build -o test main.go
# 輸出 main 方法的匯編指令
# go tool objdump -S -s main.main test 
# 輸出 namedVarReturn4 方法的匯編指令
# go tool objdump -S -s main.namedVarReturn4 test

展開看下面是截取部分匯編指令宙搬,看幾行關(guān)鍵的代碼笨腥。


0x0000 00000 (main.go:11)   TEXT    "".namedVarReturn4(SB), ABIInternal, $96-8
0x0000 00000 (main.go:11)   MOVQ    (TLS), CX
0x0009 00009 (main.go:11)   CMPQ    SP, 16(CX)
0x000d 00013 (main.go:11)   PCDATA  $0, $-2
0x000d 00013 (main.go:11)   JLS 129
0x000f 00015 (main.go:11)   PCDATA  $0, $-1
0x000f 00015 (main.go:11)   SUBQ    $96, SP
0x0013 00019 (main.go:11)   MOVQ    BP, 88(SP)
0x0018 00024 (main.go:11)   LEAQ    88(SP), BP
0x001d 00029 (main.go:11)   FUNCDATA    $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (main.go:11)   FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (main.go:11)   MOVQ    $0, "".i+104(SP)
0x0026 00038 (main.go:12)   MOVL    $8, ""..autotmp_1+8(SP)
0x002e 00046 (main.go:12)   LEAQ    "".namedVarReturn4.func1·f(SB), AX
0x0035 00053 (main.go:12)   MOVQ    AX, ""..autotmp_1+32(SP)
0x003a 00058 (main.go:12)   LEAQ    "".i+104(SP), AX
0x003f 00063 (main.go:12)   MOVQ    AX, ""..autotmp_1+80(SP)
0x0044 00068 (main.go:12)   LEAQ    ""..autotmp_1+8(SP), AX
0x0049 00073 (main.go:12)   MOVQ    AX, (SP)
0x004d 00077 (main.go:12)   PCDATA  $1, $0
0x004d 00077 (main.go:12)   CALL    runtime.deferprocStack(SB) // defer 方法入棧
0x0052 00082 (main.go:15)   TESTL   AX, AX
0x0054 00084 (main.go:15)   JNE 113
0x0056 00086 (main.go:15)   JMP 88
0x0058 00088 (main.go:16)   MOVQ    $1, "".i+104(SP) // 這里給 i 變量賦值
0x0061 00097 (main.go:16)   XCHGL   AX, AX
0x0062 00098 (main.go:16)   CALL    runtime.deferreturn(SB) // 這里執(zhí)行 return
0x0067 00103 (main.go:16)   MOVQ    88(SP), BP
0x006c 00108 (main.go:16)   ADDQ    $96, SP
0x0070 00112 (main.go:16)   RET
0x0071 00113 (main.go:12)   XCHGL   AX, AX
0x0072 00114 (main.go:12)   CALL    runtime.deferreturn(SB)// 這里執(zhí)行 defer 方法
0x0077 00119 (main.go:15)   MOVQ    88(SP), BP
0x007c 00124 (main.go:15)   ADDQ    $96, SP
0x0080 00128 (main.go:15)   RET
0x0081 00129 (main.go:15)   NOP
0x0081 00129 (main.go:11)   PCDATA  $1, $-1
0x0081 00129 (main.go:11)   PCDATA  $0, $-2
0x0081 00129 (main.go:11)   CALL    runtime.morestack_noctxt(SB)
0x0086 00134 (main.go:11)   PCDATA  $0, $-1
0x0086 00134 (main.go:11)   JMP 0

…… 省略部分

"".namedVarReturn4.func1 STEXT size=298 args=0x8 locals=0x88 funcid=0x0
    0x0000 00000 (main.go:12)   TEXT    "".namedVarReturn4.func1(SB), ABIInternal, $136-8
    0x0000 00000 (main.go:12)   MOVQ    (TLS), CX
    0x0009 00009 (main.go:12)   LEAQ    -8(SP), AX
    0x000e 00014 (main.go:12)   CMPQ    AX, 16(CX)
    0x0012 00018 (main.go:12)   PCDATA  $0, $-2
    0x0012 00018 (main.go:12)   JLS 288
    0x0018 00024 (main.go:12)   PCDATA  $0, $-1
    0x0018 00024 (main.go:12)   SUBQ    $136, SP
    0x001f 00031 (main.go:12)   MOVQ    BP, 128(SP)
    0x0027 00039 (main.go:12)   LEAQ    128(SP), BP
    0x002f 00047 (main.go:12)   FUNCDATA    $0, gclocals·2d7c1615616d4cf40d01b3385155ed6e(SB)
    0x002f 00047 (main.go:12)   FUNCDATA    $1, gclocals·7985103f61d1dca6f16bfba926a2a610(SB)
    0x002f 00047 (main.go:12)   FUNCDATA    $2, "".namedVarReturn4.func1.stkobj(SB)
    0x002f 00047 (main.go:13)   MOVQ    "".i+144(SP), AX
    0x0037 00055 (main.go:13)   TESTB   AL, (AX)
    0x0039 00057 (main.go:13)   MOVQ    "".i+144(SP), CX
    0x0041 00065 (main.go:13)   TESTB   AL, (CX)
    0x0043 00067 (main.go:13)   MOVQ    (AX), AX
    0x0046 00070 (main.go:13)   INCQ    AX
    0x0049 00073 (main.go:13)   MOVQ    AX, (CX)
    0x004c 00076 (main.go:14)   XORPS   X0, X0
    0x004f 00079 (main.go:14)   MOVUPS  X0, ""..autotmp_1+96(SP)
    0x0054 00084 (main.go:14)   MOVUPS  X0, ""..autotmp_1+112(SP)
    0x0059 00089 (main.go:14)   LEAQ    ""..autotmp_1+96(SP), AX
    0x005e 00094 (main.go:14)   MOVQ    AX, ""..autotmp_3+64(SP)
    0x0063 00099 (main.go:14)   TESTB   AL, (AX)
    0x0065 00101 (main.go:14)   LEAQ    type.string(SB), AX
    0x006c 00108 (main.go:14)   MOVQ    AX, ""..autotmp_1+96(SP)
    0x0071 00113 (main.go:14)   LEAQ    ""..stmp_1(SB), AX
    0x0078 00120 (main.go:14)   MOVQ    AX, ""..autotmp_1+104(SP)
    0x007d 00125 (main.go:14)   MOVQ    "".i+144(SP), AX
    0x0085 00133 (main.go:14)   TESTB   AL, (AX)
    0x0087 00135 (main.go:14)   MOVQ    (AX), AX
    0x008a 00138 (main.go:14)   MOVQ    AX, ""..autotmp_4+48(SP)
    0x008f 00143 (main.go:14)   MOVQ    AX, (SP)
    0x0093 00147 (main.go:14)   PCDATA  $1, $1
    0x0093 00147 (main.go:14)   CALL    runtime.convT64(SB)
    0x0098 00152 (main.go:14)   MOVQ    8(SP), AX
    0x009d 00157 (main.go:14)   MOVQ    AX, ""..autotmp_5+56(SP)
    0x00a2 00162 (main.go:14)   MOVQ    ""..autotmp_3+64(SP), CX
    0x00a7 00167 (main.go:14)   TESTB   AL, (CX)
    0x00a9 00169 (main.go:14)   LEAQ    type.int(SB), DX
    0x00b0 00176 (main.go:14)   MOVQ    DX, 16(CX)
    0x00b4 00180 (main.go:14)   LEAQ    24(CX), DI
    0x00b8 00184 (main.go:14)   PCDATA  $0, $-2
    0x00b8 00184 (main.go:14)   CMPL    runtime.writeBarrier(SB), $0
    0x00bf 00191 (main.go:14)   NOP
    0x00c0 00192 (main.go:14)   JEQ 196
    0x00c2 00194 (main.go:14)   JMP 277
    0x00c4 00196 (main.go:14)   MOVQ    AX, 24(CX)
    0x00c8 00200 (main.go:14)   JMP 202
    0x00ca 00202 (main.go:14)   PCDATA  $0, $-1
    0x00ca 00202 (main.go:14)   MOVQ    ""..autotmp_3+64(SP), AX
    0x00cf 00207 (main.go:14)   TESTB   AL, (AX)
    0x00d1 00209 (main.go:14)   JMP 211
    0x00d3 00211 (main.go:14)   MOVQ    AX, ""..autotmp_2+72(SP)
    0x00d8 00216 (main.go:14)   MOVQ    $2, ""..autotmp_2+80(SP)
    0x00e1 00225 (main.go:14)   MOVQ    $2, ""..autotmp_2+88(SP)
    0x00ea 00234 (main.go:14)   MOVQ    AX, (SP)
    0x00ee 00238 (main.go:14)   MOVQ    $2, 8(SP)
    0x00f7 00247 (main.go:14)   MOVQ    $2, 16(SP)
    0x0100 00256 (main.go:14)   PCDATA  $1, $2
    0x0100 00256 (main.go:14)   CALL    fmt.Println(SB)
    0x0105 00261 (main.go:15)   MOVQ    128(SP), BP
    0x010d 00269 (main.go:15)   ADDQ    $136, SP
    0x0114 00276 (main.go:15)   RET
    0x0115 00277 (main.go:14)   PCDATA  $0, $-2
    0x0115 00277 (main.go:14)   CALL    runtime.gcWriteBarrier(SB)
    0x011a 00282 (main.go:14)   JMP 202
    0x011c 00284 (main.go:14)   NOP
    0x011c 00284 (main.go:12)   PCDATA  $1, $-1
    0x011c 00284 (main.go:12)   PCDATA  $0, $-2
    0x011c 00284 (main.go:12)   NOP
    0x0120 00288 (main.go:12)   CALL    runtime.morestack_noctxt(SB)
    0x0125 00293 (main.go:12)   PCDATA  $0, $-1
    0x0125 00293 (main.go:12)   JMP 0
    

從上面的匯編指令里可以看出執(zhí)行流程首先會調(diào)用 deferprocStack 來創(chuàng)建 defer,然后在函數(shù)返回時 (指令 JMP 0) 插入了指令 CALL runtime.deferreturn(SB)勇垛。

知道了 defer 在流程中是通過這兩個方法是調(diào)用的脖母,知道了調(diào)用的名字,可以直接復(fù)制在 Go 源碼里全局搜索接一下 deferprocStack闲孤。

下面是 deferprocStack 的源碼

// src/runtime/panic.go

// deferprocStack queues a new deferred function with a defer record on the stack.
// The defer record must have its siz and fn fields initialized.
// All other fields can contain junk.
// The defer record must be immediately followed in memory by
// the arguments of the defer.
// Nosplit because the arguments on the stack won't be scanned
// until the defer record is spliced into the gp._defer list.
//go:nosplit
func deferprocStack(d *_defer) {
    gp := getg()
    if gp.m.curg != gp {
        // go code on the system stack can't defer
        throw("defer on system stack")
    }
    // siz and fn are already set.
    // The other fields are junk on entry to deferprocStack and
    // are initialized here.
    d.started = false
    d.heap = false
    d.openDefer = false
    d.sp = getcallersp()
    d.pc = getcallerpc()
    d.framepc = 0
    d.varp = 0
    // The lines below implement:
    //   d.panic = nil
    //   d.fd = nil
    //   d.link = gp._defer
    //   gp._defer = d
    // But without write barriers. The first three are writes to
    // the stack so they don't need a write barrier, and furthermore
    // are to uninitialized memory, so they must not use a write barrier.
    // The fourth write does not require a write barrier because we
    // explicitly mark all the defer structures, so we don't need to
    // keep track of pointers to them with a write barrier.
    *(*uintptr)(unsafe.Pointer(&d._panic)) = 0
    *(*uintptr)(unsafe.Pointer(&d.fd)) = 0
    *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
    *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))

    return0()
    // No code can go here - the C return register has
    // been set and must not be clobbered.
}

下面是 deferprocStack 方法傳入的 _defer 的源碼


// src/runtime/runtime2.go

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer and deferProcStack
// This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
// and cmd/compile/internal/gc/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
    siz     int32 // includes both arguments and results
    started bool
    heap    bool
    // openDefer indicates that this _defer is for a frame with open-coded
    // defers. We have only one defer record for the entire frame (which may
    // currently have 0, 1, or more defers active).
    openDefer bool
    sp        uintptr  // sp at time of defer
    pc        uintptr  // pc at time of defer
    fn        *funcval // can be nil for open-coded defers
    _panic    *_panic  // panic that is running defer
    link      *_defer

    // If openDefer is true, the fields below record values about the stack
    // frame and associated function that has the open-coded defer(s). sp
    // above will be the sp for the frame, and pc will be address of the
    // deferreturn call in the function.
    fd   unsafe.Pointer // funcdata for the function associated with the frame
    varp uintptr        // value of varp for the stack frame
    // framepc is the current pc associated with the stack frame. Together,
    // with sp above (which is the sp associated with the stack frame),
    // framepc/sp can be used as pc/sp pair to continue a stack trace via
    // gentraceback().
    framepc uintptr
}

下面是 deferreturn 的源碼

// src/runtime/runtime2.go

// Run a deferred function if there is one.
// The compiler inserts a call to this at the end of any
// function which calls defer.
// If there is a deferred function, this will call runtime·jmpdefer,
// which will jump to the deferred function such that it appears
// to have been called by the caller of deferreturn at the point
// just before deferreturn was called. The effect is that deferreturn
// is called again and again until there are no more deferred functions.
//
// Declared as nosplit, because the function should not be preempted once we start
// modifying the caller's frame in order to reuse the frame to call the deferred
// function.
//
// The single argument isn't actually used - it just has its address
// taken so it can be matched against pending defers.
//go:nosplit
func deferreturn(arg0 uintptr) {
    gp := getg()
    d := gp._defer
    if d == nil {
        return
    }
    sp := getcallersp()
    if d.sp != sp {
        return
    }
    if d.openDefer {
        done := runOpenDeferFrame(gp, d)
        if !done {
            throw("unfinished open-coded defers in deferreturn")
        }
        gp._defer = d.link
        freedefer(d)
        return
    }

    // Moving arguments around.
    //
    // Everything called after this point must be recursively
    // nosplit because the garbage collector won't know the form
    // of the arguments until the jmpdefer can flip the PC over to
    // fn.
    switch d.siz {
    case 0:
        // Do nothing.
    case sys.PtrSize:
        *(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
    default:
        memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz))
    }
    fn := d.fn
    d.fn = nil
    gp._defer = d.link
    freedefer(d)
    // If the defer function pointer is nil, force the seg fault to happen
    // here rather than in jmpdefer. gentraceback() throws an error if it is
    // called with a callback on an LR architecture and jmpdefer is on the
    // stack, because the stack trace can be incorrect in that case - see
    // issue #8153).
    _ = fn.fn
    jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}

上面 deferreturn 方法里最后調(diào)用里 jmpdefer 方法谆级,因為本機是64位的,所以對應(yīng) asm_amd64.s 匯編指令讼积, 下面是 jmpdefer 的實現(xiàn)


//src/runtime/asm_amd64.s

// func jmpdefer(fv *funcval, argp uintptr)
// argp is a caller SP.
// called from deferreturn.
// 1. pop the caller
// 2. sub 5 bytes from the callers return
// 3. jmp to the argument
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

配合源碼注釋以及下面相關(guān)鏈接的文檔可以理解出大概意思肥照。有興趣的可以去查閱一下相關(guān)知識再來深入了解。

3勤众、總結(jié)

最后在總結(jié)一下 defer 的流程舆绎。

  • 編譯器會把 defer 語句翻譯成對 deferprocStack 函數(shù)的調(diào)用。
  • deferprocStack 函數(shù)會把 _defer 結(jié)構(gòu)體對象并放入當(dāng)前的 goroutine 的 _defer 鏈表们颜。
  • 編譯器會在 defer 所在函數(shù)的結(jié)尾處插入對 deferreturn 的調(diào)用吕朵,deferreturn 遞歸調(diào)用 defer 語句所在函數(shù)。



相關(guān)鏈接

4窥突、了解更多

原文鏈接:Golang 中 defer 機制分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末边锁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子波岛,更是在濱河造成了極大的恐慌,老刑警劉巖音半,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件则拷,死亡現(xiàn)場離奇詭異,居然都是意外死亡曹鸠,警方通過查閱死者的電腦和手機煌茬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彻桃,“玉大人坛善,你說我怎么就攤上這事×诰欤” “怎么了眠屎?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肆饶。 經(jīng)常有香客問我改衩,道長,這世上最難降的妖魔是什么驯镊? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任葫督,我火速辦了婚禮竭鞍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橄镜。我一直安慰自己偎快,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布洽胶。 她就那樣靜靜地躺著晒夹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪妖异。 梳的紋絲不亂的頭發(fā)上惋戏,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音他膳,去河邊找鬼响逢。 笑死,一個胖子當(dāng)著我的面吹牛棕孙,可吹牛的內(nèi)容都是我干的舔亭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蟀俊,長吁一口氣:“原來是場噩夢啊……” “哼钦铺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起肢预,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤矛洞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后烫映,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沼本,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年锭沟,在試婚紗的時候發(fā)現(xiàn)自己被綠了抽兆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡族淮,死狀恐怖辫红,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祝辣,我是刑警寧澤贴妻,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站蝙斜,受9級特大地震影響揍瑟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乍炉,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一绢片、第九天 我趴在偏房一處隱蔽的房頂上張望滤馍。 院中可真熱鬧,春花似錦底循、人聲如沸巢株。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阁苞。三九已至,卻和暖如春祠挫,著一層夾襖步出監(jiān)牢的瞬間那槽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工等舔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骚灸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓慌植,卻偏偏與公主長得像甚牲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝶柿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

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