golang快速入門[4]-go語(yǔ)言如何編譯為機(jī)器碼

前文

package main
import "fmt"
func main() {
    fmt.Println("Hello, world")
}

  • 在本文中,我們將介紹初學(xué)者比較關(guān)心的話題:go語(yǔ)言如何編譯為機(jī)器碼
  • 本文的目標(biāo)是希望讀者對(duì)go語(yǔ)言的編譯過(guò)程有一個(gè)全面的理解
  • 一段程序要運(yùn)行起來(lái),需要將go代碼生成機(jī)器能夠識(shí)別的二進(jìn)制代碼
  • go代碼生成機(jī)器碼需要編譯器經(jīng)歷:
    詞法分析 => 語(yǔ)法分析 => 類型檢查 => 中間代碼 => 代碼優(yōu)化 => 生成機(jī)器碼
  • Go語(yǔ)言的編譯器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件调卑,此函數(shù)會(huì)先獲取命令行傳入的參數(shù)并更新編譯的選項(xiàng)和配置
  • 隨后就會(huì)開(kāi)始運(yùn)行 parseFiles 函數(shù)對(duì)輸入的所有文件進(jìn)行詞法與語(yǔ)法分析
func Main(archInit func(*Arch)) {
    // ...

    lines := parseFiles(flag.Args())

  • 接下來(lái)我們將對(duì)各個(gè)階段做深入介紹

詞法分析

  • 所有的編譯過(guò)程都是從解析代碼的源文件開(kāi)始的
  • 詞法分析的作用就是解析源代碼文件杉编,它將文件中的字符串序列轉(zhuǎn)換成Token序列,方便后面的處理和解析
  • 我們一般會(huì)把執(zhí)行詞法分析的程序稱為詞法解析器(lexer)
  • Token可以是關(guān)鍵字赌厅,字符串穷绵,變量名,函數(shù)名
  • 有效程序的"單詞"都由Token表示特愿,具體來(lái)說(shuō)仲墨,這意味著"package","main"揍障,"func" 等單詞都為Token
  • Go語(yǔ)言允許我們使用go/scanner和go/token包在Go程序中執(zhí)行解析程序目养,從而可以看到類似被編譯器解析后的結(jié)構(gòu)
  • 如果在語(yǔ)法解析的過(guò)程中發(fā)生了任何語(yǔ)法錯(cuò)誤,都會(huì)被語(yǔ)法解析器發(fā)現(xiàn)并將消息打印到標(biāo)準(zhǔn)輸出上毒嫡,整個(gè)編譯過(guò)程也會(huì)隨著錯(cuò)誤的出現(xiàn)而被中止
  • helloworld程序解析后如下所示
1:1   package "package"
1:9   IDENT   "main"
1:13  ;       "\n"
2:1   import  "import"
2:8   STRING  "\"fmt\""
2:13  ;       "\n"
3:1   func    "func"
3:6   IDENT   "main"
3:10  (       ""
3:11  )       ""
3:13  {       ""
4:3   IDENT   "fmt"
4:6   .       ""
4:7   IDENT   "Println"
4:14  (       ""
4:15  STRING  "\"Hello, world!\""
4:30  )       ""
4:31  ;       "\n"
5:1   }       ""
5:2   ;       "\n"
5:3   EOF     ""

  • 我們可以看到癌蚁,詞法解析器添加了分號(hào),分號(hào)常常是在C語(yǔ)言等語(yǔ)言中一條語(yǔ)句后添加的
  • 這解釋了為什么Go不需要分號(hào):詞法解析器可以智能地加入分號(hào)

語(yǔ)法分析

  • 語(yǔ)法分析的輸入就是詞法分析器輸出的 Token 序列,這些序列會(huì)按照順序被語(yǔ)法分析器進(jìn)行解析努释,語(yǔ)法的解析過(guò)程就是將詞法分析生成的 Token 按照語(yǔ)言定義好的文法(Grammar)自下而上或者自上而下的進(jìn)行規(guī)約碘梢,每一個(gè) Go 的源代碼文件最終會(huì)被歸納成一個(gè) SourceFile 結(jié)構(gòu):
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }

  • 標(biāo)準(zhǔn)的 Golang 語(yǔ)法解析器使用的就是 LALR(1) 的文法,語(yǔ)法解析的結(jié)果生成了抽象語(yǔ)法樹(Abstract Syntax Tree伐蒂,AST)
  • 抽象語(yǔ)法樹(Abstract Syntax Tree煞躬,AST),或簡(jiǎn)稱語(yǔ)法樹(Syntax tree)逸邦,是源代碼語(yǔ)法結(jié)構(gòu)的一種抽象表示恩沛。它以樹狀的形式表現(xiàn)編程語(yǔ)言的語(yǔ)法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)缕减。
  • 之所以說(shuō)語(yǔ)法是“抽象”的复唤,是因?yàn)檫@里的語(yǔ)法并不會(huì)表示出真實(shí)語(yǔ)法中出現(xiàn)的每個(gè)細(xì)節(jié)。比如烛卧,嵌套括號(hào)被隱含在樹的結(jié)構(gòu)中佛纫,并沒(méi)有以節(jié)點(diǎn)的形式呈現(xiàn);而類似于 if-condition-then 這樣的條件跳轉(zhuǎn)語(yǔ)句总放,可以使用帶有三個(gè)分支的節(jié)點(diǎn)來(lái)表示呈宇。
  • 與AST相對(duì)應(yīng)的是CST(Concrete Syntax Trees),讀者可以在參考資料中拓展閱讀二者的差別
  • 在AST中,我們能夠看到程序結(jié)構(gòu)局雄,例如函數(shù)和常量聲明
  • Go為我們提供了用于解析程序和查看AST的軟件包:go/parser 和 go/ast
  • helloworld程序生成的AST如下所示
     0  *ast.File {
     1  .  Package: 1:1
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: 1:9
     4  .  .  Name: "main"
     5  .  }
     6  .  Decls: []ast.Decl (len = 2) {
     7  .  .  0: *ast.GenDecl {
     8  .  .  .  TokPos: 3:1
     9  .  .  .  Tok: import
    10  .  .  .  Lparen: -
    11  .  .  .  Specs: []ast.Spec (len = 1) {
    12  .  .  .  .  0: *ast.ImportSpec {
    13  .  .  .  .  .  Path: *ast.BasicLit {
    14  .  .  .  .  .  .  ValuePos: 3:8
    15  .  .  .  .  .  .  Kind: STRING
    16  .  .  .  .  .  .  Value: "\"fmt\""
    17  .  .  .  .  .  }
    18  .  .  .  .  .  EndPos: -
    19  .  .  .  .  }
    20  .  .  .  }
    21  .  .  .  Rparen: -
    22  .  .  }
    23  .  .  1: *ast.FuncDecl {
    24  .  .  .  Name: *ast.Ident {
    25  .  .  .  .  NamePos: 5:6
    26  .  .  .  .  Name: "main"
    27  .  .  .  .  Obj: *ast.Object {
    28  .  .  .  .  .  Kind: func
    29  .  .  .  .  .  Name: "main"
    30  .  .  .  .  .  Decl: *(obj @ 23)
    31  .  .  .  .  }
    32  .  .  .  }
    33  .  .  .  Type: *ast.FuncType {
    34  .  .  .  .  Func: 5:1
    35  .  .  .  .  Params: *ast.FieldList {
    36  .  .  .  .  .  Opening: 5:10
    37  .  .  .  .  .  Closing: 5:11
    38  .  .  .  .  }
    39  .  .  .  }
    40  .  .  .  Body: *ast.BlockStmt {
    41  .  .  .  .  Lbrace: 5:13
    42  .  .  .  .  List: []ast.Stmt (len = 1) {
    43  .  .  .  .  .  0: *ast.ExprStmt {
    44  .  .  .  .  .  .  X: *ast.CallExpr {
    45  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr {
    46  .  .  .  .  .  .  .  .  X: *ast.Ident {
    47  .  .  .  .  .  .  .  .  .  NamePos: 6:2
    48  .  .  .  .  .  .  .  .  .  Name: "fmt"
    49  .  .  .  .  .  .  .  .  }
    50  .  .  .  .  .  .  .  .  Sel: *ast.Ident {
    51  .  .  .  .  .  .  .  .  .  NamePos: 6:6
    52  .  .  .  .  .  .  .  .  .  Name: "Println"
    53  .  .  .  .  .  .  .  .  }
    54  .  .  .  .  .  .  .  }
    55  .  .  .  .  .  .  .  Lparen: 6:13
    56  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) {
    57  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
    58  .  .  .  .  .  .  .  .  .  ValuePos: 6:14
    59  .  .  .  .  .  .  .  .  .  Kind: STRING
    60  .  .  .  .  .  .  .  .  .  Value: "\"Hello, world!\""
    61  .  .  .  .  .  .  .  .  }
    62  .  .  .  .  .  .  .  }
    63  .  .  .  .  .  .  .  Ellipsis: -
    64  .  .  .  .  .  .  .  Rparen: 6:29
    65  .  .  .  .  .  .  }
    66  .  .  .  .  .  }
    67  .  .  .  .  }
    68  .  .  .  .  Rbrace: 7:1
    69  .  .  .  }
    70  .  .  }
    71  .  }
    ..  .  .. // Left out for brevity
    83  }

  • 如上的輸出中我們能夠看出一些信息
  • 在Decls字段中甥啄,包含文件中所有聲明的列表,例如import炬搭,常量蜈漓,變量和函數(shù)
  • 為了進(jìn)一步理解,我們看一下對(duì)其的圖形化抽象表示
image
  • 紅色表示與節(jié)點(diǎn)相對(duì)應(yīng)的代碼
  • main函數(shù)包含了3個(gè)部分宫盔,名稱, 聲明, 主體
  • 名稱是單詞main的標(biāo)識(shí)
  • 由Type字段指定的聲明將包含參數(shù)列表和返回類型
  • 主體由一系列語(yǔ)句組成融虽,其中包含程序的所有行。在本例中灼芭,只有一行
  • fmt.Println語(yǔ)句由AST中的很多部分組成有额,由ExprStmt聲明。
  • ExprStmt代表一個(gè)表達(dá)式彼绷,其可以是本例中的函數(shù)調(diào)用巍佑,也可以是二進(jìn)制運(yùn)算(例如加法和減法等)
  • 我們的ExprStmt包含一個(gè)CallExpr,這是我們的實(shí)際函數(shù)調(diào)用寄悯。這又包括幾個(gè)部分萤衰,其中最重要的是FunArgs
  • Fun包含對(duì)函數(shù)調(diào)用的引用,由SelectorExpr聲明猜旬。在AST中脆栋,編譯器尚未知道fmt是一個(gè)程序包胳螟,它也可能是AST中的變量
  • Args包含一個(gè)表達(dá)式列表,這些表達(dá)式是該函數(shù)的參數(shù)筹吐。在本例中糖耸,我們已將文字字符串傳遞給函數(shù),因此它由類型為STRINGBasicLit表示丘薛。

類型檢查

  • 構(gòu)造AST之后嘉竟,將會(huì)對(duì)所有import的包進(jìn)行解析

  • 接著Go語(yǔ)言的編譯器會(huì)對(duì)語(yǔ)法樹中定義和使用的類型進(jìn)行檢查,類型檢查分別會(huì)按照順序?qū)Σ煌愋偷墓?jié)點(diǎn)進(jìn)行驗(yàn)證洋侨,按照以下的順序進(jìn)行處理:

  • 常量舍扰、類型和函數(shù)名及類型

  • 變量的賦值和初始化

  • 函數(shù)和閉包的主體

  • 哈希鍵值對(duì)的類型

  • 導(dǎo)入函數(shù)體

  • 外部的聲明

  • 通過(guò)對(duì)每一棵抽象節(jié)點(diǎn)樹的遍歷,我們?cè)诿恳粋€(gè)節(jié)點(diǎn)上都會(huì)對(duì)當(dāng)前子樹的類型進(jìn)行驗(yàn)證保證當(dāng)前節(jié)點(diǎn)上不會(huì)出現(xiàn)類型錯(cuò)誤的問(wèn)題希坚,所有的類型錯(cuò)誤和不匹配都會(huì)在這一個(gè)階段被發(fā)現(xiàn)和暴露出來(lái)边苹。

  • 類型檢查的階段不止會(huì)對(duì)樹狀結(jié)構(gòu)的節(jié)點(diǎn)進(jìn)行驗(yàn)證,同時(shí)也會(huì)對(duì)一些內(nèi)建的函數(shù)進(jìn)行展開(kāi)和改寫裁僧,例如 make 關(guān)鍵字在這個(gè)階段會(huì)根據(jù)子樹的結(jié)構(gòu)被替換成 makeslice 或者 makechan 等函數(shù)个束。

  • 類型檢查不止對(duì)類型進(jìn)行了驗(yàn)證工作,還對(duì) AST 進(jìn)行了改寫以及處理Go語(yǔ)言內(nèi)置的關(guān)鍵字

生成中間代碼

  • 在上面的步驟完成之后聊疲,可以明確代碼是正確有效的
  • 接著將AST轉(zhuǎn)換為程序的低級(jí)表示形式茬底,即靜態(tài)單一賦值形式(Static Single Assignment Form,SSA)形式获洲,核心代碼位于gc/ssa.go
  • SSA不是程序的最終狀態(tài),其可以更輕松地應(yīng)用優(yōu)化阱表,其中最重要的是始終在使用變量之前定義變量,并且每個(gè)變量只分配一次
  • 例如下面的代碼我們可以看到第一個(gè)x的賦值沒(méi)有必要的
x = 1
x = 2
y = 7

  • 編輯器會(huì)將上面的代碼變?yōu)槿缦鹿鄙海瑥亩鴷?huì)刪除x_1
x_1 = 1
x_2 = 2
y_1 = 7

  • 生成SSA的初始版本后最爬,將應(yīng)用許多優(yōu)化過(guò)程。這些優(yōu)化應(yīng)用于某些代碼段门岔,這些代碼段可以使處理器執(zhí)行起來(lái)更簡(jiǎn)單或更快速爱致。
  • 例如下面的代碼是永遠(yuǎn)不會(huì)執(zhí)行的,因此可以被消除固歪。
 if (false) {
     fmt.Println(“test”)
 }

  • 優(yōu)化的另一個(gè)示例是可以刪除某些nil檢查蒜鸡,因?yàn)榫幾g器可以證明這些檢查永遠(yuǎn)不會(huì)出錯(cuò)
  • 在對(duì)SSA進(jìn)行優(yōu)化的過(guò)程中使用了S表達(dá)式(S-expressions)進(jìn)行描述, S-expressions 是嵌套列表(樹形結(jié)構(gòu))數(shù)據(jù)的一種表示法胯努,由編程語(yǔ)言Lisp發(fā)明并普及
  • SSA優(yōu)化過(guò)程中對(duì)于S表達(dá)式的應(yīng)用如下所示牢裳,將8位的常量乘法組合起來(lái)
(Mul8 (Const8 [c]) (Const8 [d])) -> (Const8 [int64(int8(c*d))])

  • 具體的優(yōu)化包括

  • 常數(shù)傳播(constant propagation)

  • 值域傳播(value range propagation)

  • 稀疏有條件的常數(shù)傳播(sparse conditional constant propagation)

  • 消除無(wú)用的程式碼(dead code elimination)

  • 全域數(shù)值編號(hào)(global value numbering)

  • 消除部分的冗余(partial redundancy elimination)

  • 強(qiáng)度折減(strength reduction)

  • 寄存器分配(register allocation)

SSA優(yōu)化

  • 我們可以用下面的簡(jiǎn)單代碼來(lái)查看SSA及其優(yōu)化過(guò)程
  • 對(duì)于如下程序
package main

import "fmt"

func main() {
  fmt.Println(2)
}

  • 我們需要在命令行運(yùn)行如下指令來(lái)查看SSA
  • GOSSAFUNC環(huán)境變量代表我們需要查看SSA的函數(shù)并創(chuàng)建ssa.html文件
  • GOOS、GOARCH代表編譯為在Linux 64-bit平臺(tái)運(yùn)行的代碼
  • go build用-ldflags給go編譯器傳入?yún)?shù)
  • -S 標(biāo)識(shí)將打印匯編代碼
$ GOSSAFUNC=main GOOS=linux GOARCH=amd64 go build -gcflags "-S" simple.go

  • 下面的命令等價(jià)
GOSSAFUNC=main GOOS=linux GOARCH=amd64 go tool compile main.go

  • 當(dāng)打開(kāi)ssa.html時(shí)叶沛,將顯示許多代碼片段蒲讯,其中一些片段是隱藏的。
image
  • Start片段是從AST生成的SSA灰署。genssa片段是最終生成的Plan9匯編代碼
  • Start片段如下
start
b1:-
v1 (?) = InitMem <mem>
v2 (?) = SP <uintptr>
v3 (?) = SB <uintptr>
v4 (?) = Addr <*uint8> {type.int} v3
v5 (?) = Addr <*int> {""..stmp_0} v3
v6 (6) = IMake <interface {}> v4 v5 (~arg0[interface {}])
v7 (?) = ConstInterface <interface {}>
v8 (?) = ArrayMake1 <[1]interface {}> v7
v9 (6) = VarDef <mem> {.autotmp_11} v1
v10 (6) = LocalAddr <*[1]interface {}> {.autotmp_11} v2 v9
v11 (6) = Store <mem> {[1]interface {}} v10 v8 v9
v12 (6) = LocalAddr <*[1]interface {}> {.autotmp_11} v2 v11
v13 (6) = NilCheck <void> v12 v11
v14 (?) = Const64 <int> [0] (fmt..autotmp_3[int], fmt.n[int])
v15 (?) = Const64 <int> [1]
v16 (6) = PtrIndex <*interface {}> v12 v14
v17 (6) = Store <mem> {interface {}} v16 v6 v11
v18 (6) = NilCheck <void> v12 v17
v19 (6) = Copy <*interface {}> v12
v20 (6) = IsSliceInBounds <bool> v14 v15
v25 (?) = ConstInterface <error> (fmt..autotmp_4[error], fmt.err[error])
v28 (?) = OffPtr <*io.Writer> [0] v2
v29 (?) = Addr <*uint8> {go.itab.*os.File,io.Writer} v3
v30 (?) = Addr <**os.File> {os.Stdout} v3
v34 (?) = OffPtr <*[]interface {}> [16] v2
v37 (?) = OffPtr <*int> [40] v2
v39 (?) = OffPtr <*error> [48] v2
If v20 → b2 b3 (likely) (6)
b2: ← b1-
v23 (6) = Sub64 <int> v15 v14
v24 (6) = SliceMake <[]interface {}> v19 v23 v23 (fmt.a[[]interface {}])
v26 (6) = Copy <mem> v17
v27 (+6) = InlMark <void> [0] v26
v31 (274) = Load <*os.File> v30 v26
v32 (274) = IMake <io.Writer> v29 v31
v33 (274) = Store <mem> {io.Writer} v28 v32 v26
v35 (274) = Store <mem> {[]interface {}} v34 v24 v33
v36 (274) = StaticCall <mem> {fmt.Fprintln} [64] v35
v38 (274) = Load <int> v37 v36 (fmt.n[int], fmt..autotmp_3[int])
v40 (274) = Load <error> v39 v36 (fmt.err[error], fmt..autotmp_4[error])
Plain → b4 (+6)
b3: ← b1-
v21 (6) = Copy <mem> v17
v22 (6) = PanicBounds <mem> [6] v14 v15 v21
Exit v22 (6)
b4: ← b2-
v41 (7) = Copy <mem> v36
Ret v41
name ~arg0[interface {}]: v6
name fmt.a[[]interface {}]: v24
name fmt.n[int]: v14 v38
name fmt.err[error]: v25 v40
name fmt..autotmp_3[int]: v14 v38
name fmt..autotmp_4[error]: v25 v40

  • 每個(gè)v是一個(gè)新變量判帮,可以單擊以查看使用它的位置局嘁。
  • b是代碼塊,本例中我們有3個(gè)代碼塊:b1, b2和 b3
  • b1將始終被執(zhí)行晦墙,b2和b3是條件塊悦昵,如b1最后一行所示:If v20 → b2 b3 (likely) (6),只有v20為true會(huì)執(zhí)行b2晌畅,v20為false會(huì)執(zhí)行b3
  • 我們可以點(diǎn)擊v20查看其定義但指,其定義是v20 (6) = IsSliceInBounds v14 v15
  • IsSliceInBounds 會(huì)執(zhí)行如下檢查:0 <= v14 <= v15 是否成立
  • 我們可以單擊v14和v15來(lái)查看它們的定義:v14 = Const64 [0] ,v15 = Const64 [1]
  • Const64為64位常量,因此 0 <= 0 <= 1 始終成立抗楔,因此v20始終成立
  • 當(dāng)我們?cè)趏pt片段查看v20時(shí)棋凳,會(huì)發(fā)現(xiàn)v20 (6) = ConstBool [true],v20變?yōu)榱耸冀K為true
  • 因此连躏,我們會(huì)看到在opt deadcode片段中剩岳,b3塊被刪除了

代碼優(yōu)化

  • 生成SSA之后,Go編譯器還會(huì)進(jìn)行一系列簡(jiǎn)單的優(yōu)化入热,例如無(wú)效和無(wú)用代碼的刪除
  • 我們將用同樣的ssa.html文件拍棕,比較lower 和 lowered deadcode片段
image
  • 在HTML文件中,某些行顯示為灰色勺良,這意味著它們將在下一階段之一中被刪除或更改
  • 例如v15 = MOVQconst [1]為灰色莫湘,因?yàn)槠湓诤竺娓緵](méi)有被使用。MOVQconst與我們之前看到的指令Const64相同郑气,僅適用于amd64平臺(tái)

機(jī)器碼生成

  • 完成以上步驟幅垮,最終還會(huì)生成跨平臺(tái)的plan9匯編指令,并進(jìn)一步根據(jù)目標(biāo)的 CPU 架構(gòu)生成二進(jìn)制機(jī)器代碼
  • Go語(yǔ)言源代碼的 cmd/compile/internal 目錄中包含了非常多機(jī)器碼生成相關(guān)的包
  • 不同類型的 CPU 分別使用了不同的包進(jìn)行生成 amd64尾组、arm忙芒、arm64、mips讳侨、mips64呵萨、ppc64、s390x跨跨、x86 和 wasm
  • Go語(yǔ)言能夠在幾乎全部常見(jiàn)的 CPU 指令集類型上運(yùn)行潮峦。

問(wèn):go的編譯速度相對(duì)于java為什么更快

  • 快速編譯是go的設(shè)計(jì)目標(biāo)之一
  • go語(yǔ)法緊湊且規(guī)則因此更容易解析
  • go具有嚴(yán)格的依賴管理,沒(méi)有循環(huán)依賴問(wèn)題勇婴,計(jì)算依賴樹非常高效
  • 語(yǔ)言的設(shè)計(jì)易于分析忱嘹,無(wú)需符號(hào)表即可進(jìn)行解析
  • Go語(yǔ)言本身比Java簡(jiǎn)單得多,編輯器本身做的事不多
  • Go編譯器較新耕渴,其中的無(wú)用代碼更少

總結(jié)

  • 在本文中詳細(xì)介紹了go語(yǔ)言從源代碼編譯為機(jī)器碼的過(guò)程
  • 涉及了詞法分析拘悦、語(yǔ)法分析、類型檢查橱脸、SSA生成與代碼優(yōu)化础米、生成機(jī)器碼等過(guò)程
  • 以期幫助讀者全面深入的了解go語(yǔ)言的編譯過(guò)程

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末分苇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屁桑,更是在濱河造成了極大的恐慌医寿,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蘑斧,死亡現(xiàn)場(chǎng)離奇詭異糟红,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)乌叶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門盆偿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人准浴,你說(shuō)我怎么就攤上這事事扭。” “怎么了乐横?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵求橄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我葡公,道長(zhǎng)罐农,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任催什,我火速辦了婚禮涵亏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蒲凶。我一直安慰自己气筋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布旋圆。 她就那樣靜靜地躺著宠默,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灵巧。 梳的紋絲不亂的頭發(fā)上搀矫,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音刻肄,去河邊找鬼瓤球。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肄方,可吹牛的內(nèi)容都是我干的冰垄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼权她,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虹茶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起隅要,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝴罪,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后步清,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體要门,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年廓啊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了欢搜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谴轮,死狀恐怖炒瘟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情第步,我是刑警寧澤疮装,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站粘都,受9級(jí)特大地震影響廓推,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翩隧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一樊展、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堆生,春花似錦滚局、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至糯景,卻和暖如春嘁圈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟀淮。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工最住, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠惶。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓涨缚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親策治。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脓魏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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