【GO】Golang/C++混合編程 - 進(jìn)階一

文章系列
【GO】Golang/C++混合編程 - SWIG
【GO】Golang/C++混合編程 - 初識(shí)
【GO】Golang/C++混合編程 - 入門(mén)
【GO】Golang/C++混合編程 - 基礎(chǔ)
【GO】Golang/C++混合編程 - 進(jìn)階一
【GO】Golang/C++混合編程 - 進(jìn)階二
【GO】Golang/C++混合編程 - 實(shí)戰(zhàn)

Golang/C++混合編程

編譯過(guò)程

GO 調(diào)用 C

對(duì)于比較簡(jiǎn)單的 CGO 代碼我們可以直接通過(guò)手動(dòng)調(diào)用go tool cgo命令來(lái)查看生成的中間文件。

// 例:
package main

//int sum(int a, int b) { return a+b; }
import "C"

func main() {
    println(C.sum(1, 1))
}
go tool cgo main.go
# 生成的中間文件目錄

$ ls _obj | awk '{print $NF}'
_cgo_.o
_cgo_export.c
_cgo_export.h
_cgo_flags
_cgo_gotypes.go
_cgo_main.c
main.cgo1.go
main.cgo2.c

# 其中_cgo_.o爱态、_cgo_flags和_cgo_main.c文件和我們的代碼沒(méi)有直接的邏輯關(guān)聯(lián),可以暫時(shí)忽略
// main.cgo1.go,它是`main.go`文件展開(kāi)虛擬 C 包相關(guān)函數(shù)和變量后的 GO 代碼

package main
//int sum(int a, int b) { return a+b; }

import _ "unsafe"
func main() {
    println((_Cfunc_sum)(1, 1))
}

// 其中`C.sum(1, 1)`函數(shù)調(diào)用被替換成了`(_Cfunc_sum)(1, 1)`, 每一個(gè)`C.xxx`形式的函數(shù)都會(huì)被替換為`_Cfunc_xxx`格式的純 GO 函數(shù),其中前綴`_Cfunc_`表示這是一個(gè)C函數(shù),對(duì)應(yīng)一個(gè)私有的 GO 橋接函數(shù)
// _cgo_gotypes.go

//go:cgo_unsafe_args
func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
    _cgo_runtime_cgocall(_cgo_506f45f9fa85_Cfunc_sum, uintptr(unsafe.Pointer(&p0)))
    if _Cgo_always_false {
        _Cgo_use(p0)
        _Cgo_use(p1)
    }
    return
}

// `_Cfunc_sum`函數(shù)在 CGO 生成的
// 其參數(shù)和返回值`_Ctype_int`類型對(duì)應(yīng)`C.int`類型秦忿,命名的規(guī)則和`_Cfunc_xxx`類似默赂,不同的前綴用于區(qū)分函數(shù)和類型
// 
// 其中`_cgo_runtime_cgocall`對(duì)應(yīng)`runtime.cgocall`函數(shù)沛鸵,函數(shù)的聲明如下:
// func runtime.cgocall(fn, arg unsafe.Pointer) int32
// 第一個(gè)參數(shù)是 C 語(yǔ)言函數(shù)的地址,第二個(gè)參數(shù)是存放 C 語(yǔ)言函數(shù)對(duì)應(yīng)的參數(shù)結(jié)構(gòu)體的地址
// main.cgo2.c
// 
// 被傳入C語(yǔ)言函數(shù)`_cgo_506f45f9fa85_Cfunc_sum`也是 CGO 生成的中間函數(shù)
void _cgo_506f45f9fa85_Cfunc_sum(void *v) {
    struct {
        int p0;
        int p1;
        int r;
        char __pad12[4];
    } __attribute__((__packed__)) *a = v;
    char *stktop = _cgo_topofstack();
    __typeof__(a->r) r;
    _cgo_tsan_acquire();
    r = sum(a->p0, a->p1);
    _cgo_tsan_release();
    a = (void*)((char*)a + (_cgo_topofstack() - stktop));
    a->r = r;
}
// 這個(gè)函數(shù)參數(shù)只有一個(gè)void范型的指針缆八,函數(shù)沒(méi)有返回值曲掰。真實(shí)的sum函數(shù)的函數(shù)參數(shù)和返回值均通過(guò)唯一的參數(shù)指針類實(shí)現(xiàn)
//
// _cgo_506f45f9fa85_Cfunc_sum函數(shù)的指針指向的結(jié)構(gòu)為:
//      struct {
//         int p0;
//         int p1;
//         int r;
//         char __pad12[4];
//     } __attribute__((__packed__)) *a = v;
// 其中p0成員對(duì)應(yīng)sum的第一個(gè)參數(shù),p1成員對(duì)應(yīng)sum的第二個(gè)參數(shù)奈辰,r成員栏妖,__pad12用于填充結(jié)構(gòu)體保證對(duì)齊CPU機(jī)器字的整倍數(shù)
// 然后從參數(shù)指向的結(jié)構(gòu)體獲取調(diào)用參數(shù)后開(kāi)始調(diào)用真實(shí)的C語(yǔ)言版sum函數(shù),并且將返回值保持到結(jié)構(gòu)體內(nèi)返回值對(duì)應(yīng)的成員

因?yàn)?GO 語(yǔ)言和 C 語(yǔ)言有著不同的內(nèi)存模型和函數(shù)調(diào)用規(guī)范奖恰。其中_cgo_topofstack函數(shù)相關(guān)的代碼用于 C 函數(shù)調(diào)用后恢復(fù)調(diào)用棧吊趾。_cgo_tsan_acquire_cgo_tsan_release則是用于掃描 CGO 相關(guān)的函數(shù)則是對(duì) CGO 相關(guān)函數(shù)的指針做相關(guān)檢查宛裕,C.sum的整個(gè)調(diào)用流程圖如下:

cgo1.png

C 調(diào)用 GO

package main

//int sum(int a, int b);
import "C"

//export sum
func sum(a, b C.int) C.int {
    return a + b
}

func main() {}

// 同上一講,export 關(guān)鍵字论泛,指明 sum 函數(shù)是導(dǎo)出的揩尸,可以被 C 代碼調(diào)用
// go build -buildmode=c-archive -o sum.a sum.go -> 編譯為 C 靜態(tài)庫(kù)
// 此時(shí)會(huì)生成一個(gè)`sum.a`靜態(tài)庫(kù)和`sum.h`頭文件
go tool cgo main.go
# 生成的中間文件目錄

$ ls _obj | awk '{print $NF}'
_cgo_export.c
_cgo_export.h
_cgo_gotypes.go
main.cgo1.go
main.cgo2.c

# 其中僅包含了需要關(guān)心的文件

其中_cgo_export.h文件的內(nèi)容和生成C靜態(tài)庫(kù)時(shí)產(chǎn)生的sum.h頭文件是同一個(gè)文件,里面同樣包含sum函數(shù)的聲明

// _cgo_export.c

int sum(int p0, int p1)
{
    __SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done();
    struct {
        int p0;
        int p1;
        int r0;
        char __pad0[4];
    } __attribute__((__packed__)) a;
    a.p0 = p0;
    a.p1 = p1;
    _cgo_tsan_release();
    crosscall2(_cgoexp_8313eaf44386_sum, &a, 16, _cgo_ctxt);
    _cgo_tsan_acquire();
    _cgo_release_context(_cgo_ctxt);
    return a.r0;
}

// sum 函數(shù)的內(nèi)容采用和前面類似的技術(shù)屁奏,將 sum 函數(shù)的參數(shù)和返回值打包到一個(gè)結(jié)構(gòu)體中岩榆,然后通過(guò)`runtime/cgo.crosscall2`函數(shù)將結(jié)構(gòu)體傳給`_cgoexp_8313eaf44386_sum`函數(shù)執(zhí)行
//
// `runtime/cgo.crosscall2`函數(shù)采用匯編語(yǔ)言實(shí)現(xiàn),它對(duì)應(yīng)的函數(shù)聲明如下
// func runtime/cgo.crosscall2(
//     fn func(a unsafe.Pointer, n int32, ctxt uintptr),
//     a unsafe.Pointer, n int32,
//     ctxt uintptr,
// )
// fn是中間代理函數(shù)的指針了袁,a是對(duì)應(yīng)調(diào)用參數(shù)和返回值的結(jié)構(gòu)體指針
// 中間的`_cgoexp_8313eaf44386_sum`代理函數(shù)在`_cgo_gotypes.go`文件
// _cgo_gotypes.go

func _cgoexp_8313eaf44386_sum(a unsafe.Pointer, n int32, ctxt uintptr) {
    fn := _cgoexpwrap_8313eaf44386_sum
    _cgo_runtime_cgocallback(**(**unsafe.Pointer)(unsafe.Pointer(&fn)), a, uintptr(n), ctxt);
}
func _cgoexpwrap_8313eaf44386_sum(p0 _Ctype_int, p1 _Ctype_int) (r0 _Ctype_int) {
    return sum(p0, p1)
}
// 內(nèi)部將 sum 的包裝函數(shù)`_cgoexpwrap_8313eaf44386_sum`作為函數(shù)指針朗恳,然后由`_cgo_runtime_cgocallback`函數(shù)完成 C 語(yǔ)言到 GO 函數(shù)的回調(diào)工作
// `_cgo_runtime_cgocallback`函數(shù)對(duì)應(yīng)`runtime.cgocallback`函數(shù),函數(shù)的類型如下:
// func runtime.cgocallback(fn, frame unsafe.Pointer, framesize, ctxt uintptr)

其調(diào)用流程载绿,大致如下:


cgo2.png

內(nèi)存模型

CGO 是架接 GO 語(yǔ)言和 C 語(yǔ)言的橋梁粥诫,它使二者在二進(jìn)制接口層面實(shí)現(xiàn)了互通,但是我們要注意因兩種語(yǔ)言的內(nèi)存模型的差異而可能引起的問(wèn)題崭庸。如果在 CGO 處理的跨語(yǔ)言函數(shù)調(diào)用時(shí)涉及到了指針的傳遞怀浆,則可能會(huì)出現(xiàn) GO 語(yǔ)言和 C 語(yǔ)言共享某一段內(nèi)存的場(chǎng)景。我們知道 C 語(yǔ)言的內(nèi)存在分配之后就是穩(wěn)定的怕享,但是 GO 語(yǔ)言因?yàn)楹瘮?shù)棧的動(dòng)態(tài)伸縮可能導(dǎo)致棧中內(nèi)存地址的移動(dòng)执赡。如果 C 語(yǔ)言持有的是移動(dòng)之前的 GO 指針,那么以舊指針訪問(wèn) GO 對(duì)象時(shí)會(huì)導(dǎo)致程序崩潰函筋。

GO 訪問(wèn) C 內(nèi)存

C語(yǔ)言空間的內(nèi)存是穩(wěn)定的沙合,只要不是被人為提前釋放,那么在Go語(yǔ)言空間可以放心大膽地使用跌帐。

package main
/*
#include <stdlib.h>
void* makeslice(size_t memsize) {
    return malloc(memsize);
}
*/
import "C"
import "unsafe"

func makeByteSlize(n int) []byte {
    p := C.makeslice(C.size_t(n))
    return ((*[1 << 31]byte)(p))[0:n:n]
}

func freeByteSlice(p []byte) {
    C.free(unsafe.Pointer(&p[0]))
}

func main() {
    s := makeByteSlize(1<<32+1)
    s[len(s)-1] = 255
    print(s[len(s)-1])
    freeByteSlice(s)
}
// 我們通過(guò)`makeByteSlize`來(lái)創(chuàng)建大于4G內(nèi)存大小的切片首懈,從而繞過(guò)了 GO 語(yǔ)言實(shí)現(xiàn)的限制(需要代碼驗(yàn)證)。而`freeByteSlice`輔助函數(shù)則用于釋放從 C 語(yǔ)言函數(shù)創(chuàng)建的切片谨敛。

C 訪問(wèn) GO 內(nèi)存

參考值傳遞究履,每次都對(duì)內(nèi)存進(jìn)行拷貝:

package main
/*
void printString(const char* s) {
    printf("%s", s);
}
*/
import "C"

func printString(s string) {
    cs := C.CString(s)
    defer C.free(unsafe.Pointer(cs))
    C.printString(cs)
}

func main() {
    s := "hello"
    printString(s)
}

在 CGO 調(diào)用的 C 語(yǔ)言函數(shù)返回前,CGO 保證傳入的 Go 語(yǔ)言內(nèi)存在此期間不會(huì)發(fā)生移動(dòng)脸狸,C 語(yǔ)言函數(shù)可以大膽地使用 GO 語(yǔ)言的內(nèi)存

package main
/*
#include<stdio.h>
void printString(const char* s, int n) {
    int i;
    for(i = 0; i < n; i++) {
        putchar(s[i]);
    }
    putchar('\n');
}
*/
import "C"

func printString(s string) {
    p := (*reflect.StringHeader)(unsafe.Pointer(&s))
    C.printString((*C.char)(unsafe.Pointer(p.Data)), C.int(len(s)))
}

func main() {
    s := "hello"
    printString(s)
}

我們通過(guò)reflect.StringHeader結(jié)構(gòu)體來(lái)獲取字符串的指針和長(zhǎng)度最仑,然后直接將指針和長(zhǎng)度傳遞給 C 語(yǔ)言函數(shù)。
但此時(shí)存在幾個(gè)可能發(fā)生的問(wèn)題炊甲,比如字符串的長(zhǎng)度超過(guò) C 語(yǔ)言函數(shù)的參數(shù)限制泥彤,或者字符串的長(zhǎng)度大于 C 語(yǔ)言函數(shù)的棧空間卿啡,那么 C 語(yǔ)言函數(shù)就會(huì)發(fā)生棧溢出吟吝;另外,如果 C 語(yǔ)言函數(shù)持有這塊內(nèi)存過(guò)久牵囤,這會(huì)導(dǎo)致 GO 語(yǔ)言內(nèi)協(xié)程注冊(cè)的棧內(nèi)存無(wú)法被回收爸黄,從而導(dǎo)致這個(gè)協(xié)程阻塞,并影響 GO 語(yǔ)言 GC 的效率揭鳞。

C 長(zhǎng)期持有 GO 指針對(duì)象

當(dāng) C 語(yǔ)言調(diào)用 GO 函數(shù)時(shí)炕贵,若存在返回值情況,那么此時(shí) GO 對(duì)象內(nèi)存的生命周期就超出了 GO 語(yǔ)言的管理范圍野崇,因?yàn)?C 語(yǔ)言可能正在使用這塊內(nèi)存称开,此時(shí)如果 GO 語(yǔ)言 GC 回收了這塊內(nèi)存,那么 C 語(yǔ)言就會(huì)訪問(wèn)到一塊無(wú)效的內(nèi)存乓梨,導(dǎo)致程序崩潰鳖轰。所以,我們不能在 C 語(yǔ)言中直接使用 GO 語(yǔ)言對(duì)象的內(nèi)存扶镀。
但是這種情況是存在的蕴侣,比如 C 語(yǔ)言需要持有 GO 語(yǔ)言對(duì)象的內(nèi)存,并在后續(xù)的調(diào)用中使用臭觉,那么這種情況下昆雀,我們需要將內(nèi)存拷貝到 C 語(yǔ)言中,或者借鑒內(nèi)存管理的思路蝠筑,讓 C 語(yǔ)言和 GO 語(yǔ)言共同管理這塊內(nèi)存(GO 語(yǔ)言提供方法給 C 語(yǔ)言使用)狞膘。
為了減少拷貝帶來(lái)的性能開(kāi)銷,我們主要使用方法二什乙,其例子如下:

package main

import "sync"

type ObjectId int32

var refs struct {
    sync.Mutex
    objs map[ObjectId]interface{}
    next ObjectId
}

func init() {
    refs.Lock()
    defer refs.Unlock()
    refs.objs = make(map[ObjectId]interface{})
    refs.next = 1000
}

func NewObjectId(obj interface{}) ObjectId {
    refs.Lock()
    defer refs.Unlock()
    id := refs.next
    refs.next++
    refs.objs[id] = obj
    return id
}

func (id ObjectId) IsNil() bool {
    return id == 0
}

func (id ObjectId) Get() interface{} {
    refs.Lock()
    defer refs.Unlock()
    return refs.objs[id]
}

func (id *ObjectId) Free() interface{} {
    refs.Lock()
    defer refs.Unlock()
    obj := refs.objs[*id]
    delete(refs.objs, *id)
    *id = 0
    return obj
}

上述代碼可以看到挽封,我們通過(guò)一個(gè)map來(lái)管理Go語(yǔ)言對(duì)象和id對(duì)象的映射關(guān)系。其中NewObjectId用于創(chuàng)建一個(gè)和對(duì)象綁定的id臣镣,而id對(duì)象的方法可用于解碼出原始的Go對(duì)象辅愿,也可以用于結(jié)束id和原始Go對(duì)象的綁定。
下面一組函數(shù)以C接口規(guī)范導(dǎo)出退疫,可以被C語(yǔ)言函數(shù)調(diào)用:

package main

/*
extern char* NewGoString(char* );
extern void FreeGoString(char* );
extern void PrintGoString(char* );
static void printString(const char* s) {
    char* gs = NewGoString(s);
    PrintGoString(gs);
    FreeGoString(gs);
}
*/
import "C"

//export NewGoString
func NewGoString(s *C.char) *C.char {
    gs := C.GoString(s)
    id := NewObjectId(gs)
    return (*C.char)(unsafe.Pointer(uintptr(id)))
}

//export FreeGoString
func FreeGoString(p *C.char) {
    id := ObjectId(uintptr(unsafe.Pointer(p)))
    id.Free()
}

//export PrintGoString
func PrintGoString(s *C.char) {
    id := ObjectId(uintptr(unsafe.Pointer(p)))
    gs := id.Get().(string)
    print(gs)
}

func main() {
    C.printString("hello")
}

printString函數(shù)中渠缕,我們通過(guò)NewGoString創(chuàng)建一個(gè)對(duì)應(yīng)的 GO 字符串對(duì)象,返回的其實(shí)是一個(gè) id褒繁,不能直接使用亦鳞。我們借助PrintGoString函數(shù)將 id 解析為 GO 語(yǔ)言字符串后打印。該字符串在 C 語(yǔ)言函數(shù)中完全跨越了 GO 語(yǔ)言的內(nèi)存管理棒坏,在PrintGoString調(diào)用前即使發(fā)生了棧伸縮導(dǎo)致的 GO 字符串地址發(fā)生變化也依然可以正常工作燕差,因?yàn)樵撟址畬?duì)應(yīng)的 id 是穩(wěn)定的,在 GO 語(yǔ)言空間通過(guò) id 解碼得到的字符串也就是有效的坝冕。

導(dǎo)出 C 函數(shù)不能返回 GO 內(nèi)存

在 GO 語(yǔ)言中徒探,GO 是從一個(gè)固定的虛擬地址空間分配內(nèi)存。而 C 語(yǔ)言分配的內(nèi)存則不能使用 GO 語(yǔ)言保留的虛擬內(nèi)存空間喂窟。在CGO 環(huán)境测暗,GO 語(yǔ)言運(yùn)行時(shí)默認(rèn)會(huì)檢查導(dǎo)出返回的內(nèi)存是否是由 GO 語(yǔ)言分配的央串,如果是則會(huì)拋出運(yùn)行時(shí)異常。

/*
extern int* getGoPtr();
static void Main() {
    int* p = getGoPtr();
    *p = 42;
}
*/
import "C"

func main() {
    C.Main()
}

//export getGoPtr
func getGoPtr() *C.int {
    return new(C.int)
}

其中getGoPtr返回的雖然是 C 語(yǔ)言類型的指針碗啄,但是內(nèi)存本身是從 GO 語(yǔ)言的new函數(shù)分配质和,也就是由 GO 語(yǔ)言運(yùn)行時(shí)統(tǒng)一管理的內(nèi)存。然后我們?cè)?C 語(yǔ)言的Main函數(shù)中調(diào)用了getGoPtr函數(shù)稚字,此時(shí)默認(rèn)將發(fā)送運(yùn)行時(shí)異常

$ go run main.go

panic: runtime error: cgo result has Go pointer
goroutine 1 [running]:
main._cgoexpwrap_cfb3840e3af2_getGoPtr.func1(0xc420051dc0)
  command-line-arguments/_obj/_cgo_gotypes.go:60 +0x3a
main._cgoexpwrap_cfb3840e3af2_getGoPtr(0xc420016078)
  command-line-arguments/_obj/_cgo_gotypes.go:62 +0x67
main._Cfunc_Main()
  command-line-arguments/_obj/_cgo_gotypes.go:43 +0x41
main.main()
  /Users/chai/go/src/github.com/chai2010 \
  /advanced-go-programming-book/examples/ch2-xx \
  /return-go-ptr/main.go:17 +0x20
exit status 2

異常說(shuō)明 CGO 函數(shù)返回的結(jié)果中含有 GO 語(yǔ)言分配的指針饲宿。指針的檢查操作發(fā)生在 C 語(yǔ)言版的getGoPtr函數(shù)中,它是由 CGO 生成的橋接 C 語(yǔ)言和 GO 語(yǔ)言的函數(shù)胆描。
下面是cgo生成的C語(yǔ)言版本getGoPtr函數(shù)的具體細(xì)節(jié)(在cgo生成的_cgo_export.c文件定義):

int* getGoPtr()
{
    __SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done();
    struct {
        int* r0;
    } __attribute__((__packed__)) a;
    _cgo_tsan_release();
    crosscall2(_cgoexp_95d42b8e6230_getGoPtr, &a, 8, _cgo_ctxt);
    _cgo_tsan_acquire();
    _cgo_release_context(_cgo_ctxt);
    return a.r0;
}

其中_cgo_tsan_acquire是從LLVM項(xiàng)目移植過(guò)來(lái)的內(nèi)存指針掃描函數(shù)瘫想,它會(huì)檢查 CGO 函數(shù)返回的結(jié)果是否包含 GO 指針。
需要說(shuō)明的是昌讲,CGO 默認(rèn)對(duì)返回結(jié)果的指針的檢查是有代價(jià)的国夜,特別是 CGO 函數(shù)返回的結(jié)果是一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)時(shí)將花費(fèi)更多的時(shí)間。如果已經(jīng)確保了 CGO 函數(shù)返回的結(jié)果是安全的話短绸,可以通過(guò)設(shè)置環(huán)境變量GODEBUG=cgocheck=0來(lái)關(guān)閉指針檢查行為支竹。
其中,0:關(guān)閉鸠按,1:默認(rèn)礼搁,2:更嚴(yán)格的檢查。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末目尖,一起剝皮案震驚了整個(gè)濱河市馒吴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瑟曲,老刑警劉巖饮戳,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洞拨,居然都是意外死亡扯罐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)烦衣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)歹河,“玉大人,你說(shuō)我怎么就攤上這事花吟〗掌纾” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵衅澈,是天一觀的道長(zhǎng)键菱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)今布,這世上最難降的妖魔是什么经备? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任拭抬,我火速辦了婚禮,結(jié)果婚禮上侵蒙,老公的妹妹穿的比我還像新娘玖喘。我一直安慰自己,他們只是感情好蘑志,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著贬派,像睡著了一般急但。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搞乏,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天波桩,我揣著相機(jī)與錄音,去河邊找鬼请敦。 笑死镐躲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侍筛。 我是一名探鬼主播萤皂,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼匣椰!你這毒婦竟也來(lái)了裆熙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤禽笑,失蹤者是張志新(化名)和其女友劉穎入录,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體佳镜,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡僚稿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蟀伸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚀同。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖啊掏,靈堂內(nèi)的尸體忽然破棺而出唤崭,到底是詐尸還是另有隱情,我是刑警寧澤脖律,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布谢肾,位于F島的核電站,受9級(jí)特大地震影響小泉,放射性物質(zhì)發(fā)生泄漏芦疏。R本人自食惡果不足惜冕杠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酸茴。 院中可真熱鬧分预,春花似錦、人聲如沸薪捍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)酪穿。三九已至凳干,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間被济,已是汗流浹背救赐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留只磷,地道東北人经磅。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钮追,于是被迫代替她去往敵國(guó)和親预厌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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