Chapter 1. go 匯編入門

$ go version
go version go1.10 linux/amd644

第一章: Go 編譯入門

在開始深入研究運行時類庫和標(biāo)準(zhǔn)類庫之前古戴,學(xué)習(xí)一些go的抽象匯編語言是有必要的罩句。這份快速指引希望能助你提速财著。

目錄

  • 偽匯編
  • 拆解一個簡單程序
  • 分析goroutines, stacks 和 splits
  • 總結(jié)
  • 參考文獻

  • 這篇文章需要基礎(chǔ)的匯編知識
  • 如果涉及到機器架構(gòu),假定都是 linux/amd64
  • 我們將一直使用編譯器優(yōu)化項
  • 除了特別說明扰才,引用文字或者注釋基本來自于官方文檔或者codebase

偽匯編

Go編譯器輸出一種抽象同波、簡便格式的匯編鳄梅,這種匯編并不適合任何真實的硬件。Go匯編使用這種偽匯編輸出 適用于不同的機器架構(gòu)未檩。
這種設(shè)計有很多好處戴尸,其中最主要的是go可以很容易地適應(yīng)一種新的架構(gòu)。要看更多信息冤狡,Rob Pike 的 《The Design of the Go Assembler》這本書里面有講到校赤。文末參考文獻里面也有羅列。

關(guān)于Go匯編筒溃,最需要知道的一點是它并不依賴于一個具體的機器。很多東西跟機器有映射沾乘,但是有些不是怜奖。這是因為編譯器組件在執(zhí)行過程中并不需要匯編校驗通過。而是編譯器操作是基于一堆半抽象指令集翅阵,指令選擇有一部分發(fā)生在代碼生成之后歪玲,所以當(dāng)你看到一個指令MOV 可能不是move 這個指令迁央,有可能是clear 或者load . 或者在某些機器架構(gòu)中就跟他的名字一樣的含義。一般來說 機器相關(guān)的操作傾向于跟他們展示的含義一樣滥崩, 而像跟內(nèi)存移動岖圈、子程序調(diào)用與返回等相關(guān)的指令則更抽象。這些細節(jié)隨機器架構(gòu)調(diào)整钙皮,我們?yōu)檫@種不精確道歉蜂科,到目前位置,并沒有一個很好的解決方案短条。

匯編語言是一種解析半抽象指令描述指令集 的途徑导匣,他可以將半抽象指令集轉(zhuǎn)換成輸出到鏈接器的指令。

拆解一個簡單程序

看下面這段代碼(direct_topfunc_call.go):

//go:noinline
func add(a, b int32) (int32, bool) { 
  return a + b, true 
}
func main() { 
  add(10, 32) 
}

(注意 //go:noinline 是編譯器指令茸时,不要省略)
把這個程序編譯成匯編:

$ GOOS=linux GOARCH=amd64 go tool compile -S direct_topfunc_call.go
0x0000 TEXT     "".add(SB), NOSPLIT, $0-16
  0x0000 FUNCDATA   $0, gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)
  0x0000 FUNCDATA   $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x0000 MOVL       "".b+12(SP), AX
  0x0004 MOVL       "".a+8(SP), CX
  0x0008 ADDL       CX, AX
  0x000a MOVL       AX, "".~r2+16(SP)
  0x000e MOVB       $1, "".~r3+20(SP)
  0x0013 RET

0x0000 TEXT     "".main(SB), $24-0
  ;; ...省略stack-split 相關(guān)的開始部分...
  0x000f SUBQ       $24, SP
  0x0013 MOVQ       BP, 16(SP)
  0x0018 LEAQ       16(SP), BP
  0x001d FUNCDATA   $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x001d FUNCDATA   $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
  0x001d MOVQ       $137438953482, AX
  0x0027 MOVQ       AX, (SP)
  0x002b PCDATA     $0, $0
  0x002b CALL       "".add(SB)
  0x0030 MOVQ       16(SP), BP
  0x0035 ADDQ       $24, SP
  0x0039 RET
  ;; ...省略stack-split 相關(guān)的結(jié)尾部分...

為了更好地了解匯編器做了什么贡定,我們將逐行分析這兩個方法。

分析 add

0x0000 TEXT "".add(SB), NOSPLIT, $0-16
  • 0x0000: 當(dāng)前指令的偏移可都,程序的開始
  • TEXT "".add: TEXT 表明 字符 "".add 符號作為.text端(程序段) 的一部分缓待,指示 之后的指令是function的一部分。
  • (SB): SB 是一個存儲“靜態(tài)基地址”指針的虛擬寄存器渠牲,即存儲我們程序空間的開始地址
  • "".add(SB) 標(biāo)識我們的字符位于距離程序空間開始處固定偏移量的位置旋炒。換言之,它有一個絕對地址:這是個全局函數(shù)變量嘱兼。objdump將會驗證這一切国葬。
$ objdump -j .text -t direct_topfunc_call | grep 'main.add'
000000000044d980 g     F .text  000000000000000f main.add

所有用戶定義變量都是以為寄存器FP(參數(shù)和本地變量)和SB(全局變量)為基準(zhǔn),找到對應(yīng)的偏移量而被寫入芹壕。SB偽寄存器被認(rèn)為是內(nèi)存的起始地址汇四,所以foo(SB) 就是foo在內(nèi)存中的地址。

  • NOSPLIT: 告知編譯器不要將其插入stack-split的前導(dǎo)指令踢涌, 這用來檢查當(dāng)前的棧是否需要擴大通孽。
    在我們這個例子中, 編譯器自己設(shè)置了一些flag:很容易就能看出來睁壁,因為add方法沒有本地變量也沒有自己的棧幀背苦,它不會超過當(dāng)前的棧;因此在每次函數(shù)調(diào)用時踐行檢查是純粹浪費CPU指令周期潘明。

"NOSPLIT": 在棧必須分割時去檢查不要插入到前導(dǎo)指令行剂。 協(xié)程的棧幀,包括協(xié)程所調(diào)用的那些钳降,必須位于堆棧段中一段空閑空間的頂部厚宰。用來保護類似用來棧分配代碼的協(xié)程,

本章文末,我們會有一個關(guān)于go 協(xié)程跟棧分配的簡單介紹铲觉。

  • $0-16$0 標(biāo)識將要在占空間分配的字節(jié)數(shù)澈蝙;而$16 表示傳參所占的空間

通常,幀大小后面緊跟著參數(shù)大小撵幽,被“-”分割(這并不是一個減號灯荧,而是一個語法約定)。幀大小 $24-8 標(biāo)識這個方法有一個24字節(jié)的幀空間盐杂,如果要調(diào)用它需要8字節(jié)的參數(shù)大小逗载,這參數(shù)大小分配在調(diào)用方的幀空間中。

0x0000 FUNCDATA $0, gclocals·f207267fbf96a0178e8758c6e3e0ce28(SB)
0x0000 FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)

FUNCDATA 和PCDATA 表示了垃圾回收所需要的信息况褪;編譯器用這些信息撕贞。
關(guān)于這部分現(xiàn)在不要關(guān)注;當(dāng)討論垃圾回收的時候测垛,我們將回過頭來了解這部分捏膨。

0x0000 MOVL "".b+12(SP), AX
0x0004 MOVL "".a+8(SP), CX

Go調(diào)用過程 規(guī)定,所有的參數(shù)必須加載到棧中食侮,使用調(diào)用者預(yù)分配的椇叛模空間。
調(diào)用方需要適當(dāng)?shù)財U大或者縮小棧大小锯七,以保證參數(shù)可以傳遞到被調(diào)用方法链快,以及接受來自被調(diào)用者的返回。
Go編譯器并沒有產(chǎn)生PUSH/POP家族的指令眉尸,而是通過增大或者縮小硬件棧指針SP來進行棧的縮小或者擴大的域蜗。
【更新: 在issue #21: about SP register中我們建一個討論了這個問題≡牖】

SP偽寄存器是一個用于指示本地變量和傳參的偽寄存器霉祸。它指向本地棧的頂部,所以引用應(yīng)該使用范圍在 [?framesize, 0) 的負(fù)偏移量袱蜡,比如x-8(SP), y-4(SP) 等等丝蹭。

雖然官方文檔生成“所有的用戶定義的變量都是以偽寄存器FP為基礎(chǔ)進行偏移(參數(shù)和本地變量)”,對手寫代碼適合坪蚁。
像很多現(xiàn)代編譯器奔穿,Go 工具集經(jīng)常采用以代碼生成地址為基準(zhǔn)的偏移量,進行引用參數(shù)和本地變量敏晤。這使得平臺可以用很少的指令 將幀指針(frame-pointer) 用作額外的一般用途寄存器(比如像x86)贱田。
如果感興趣,你可以看下在文末文章中介紹的x86-64 棧幀布局嘴脾。
【更新:在issue #2: Frame pointer中我們討論了這個問題】
"".b+12(SP)"".a+8(SP)分別指向棧的低12字節(jié)和低8字節(jié)的地址男摧。
.a.b 就是一個隨意指定的別名;雖然他們沒有具體的文法含義,但是當(dāng)使用虛擬存儲器的相對地址時彩倚,他們是必須的。虛擬frame-pointer 的文中提到了這一點:

FP 偽寄存器是一個虛擬指針扶平,用來指示方法的參數(shù)帆离。編譯器維護一份虛擬指針,指向棧中的參數(shù)结澄,這些參數(shù)的地址是從偽指針之后的偏移量哥谷。因此0(FP)是方法的第一個參數(shù), 8(FP)在第二個等等(在64位機器上)麻献。如果以這種方法指示方法的參數(shù)们妥,需要在開始處放置一個名字,比如 first_arg+0(FP) 和 second_arg+8(FP).(偏移量的意思是從frame pointer開始的偏移量勉吻,到SB的距離监婶,這就是一個變量的偏移量)。編譯器遵循這種約定齿桃,拒絕類似0(FP)和 8(FP). 具體的名字是預(yù)發(fā)無關(guān)的惑惶,但是需要被用來只是變量的名字。

最后短纵,有兩點需要注意:

  1. 第一個參數(shù)a并不是位于0(SP)带污, 而是位于8(SP);這是因為調(diào)用方通過CALL這個偽指令使用了0(SP)這個地址香到。
  2. 參數(shù)是以一個相反的順序傳遞鱼冀,比如第一個參數(shù)里棧頂更近。
0x0008 ADDL CX, AX
0x000a MOVL AX, "".~r2+16(SP)
0x000e MOVB $1, "".~r3+20(SP)

ADDL 執(zhí)行了兩個長變量(占了4字節(jié)的值)的相加操作悠就,并且將結(jié)果存儲在AX中千绪。
這個結(jié)果被移到 "".~r2+16(SP), 這個地址是調(diào)用者在棧空間中預(yù)占的空間理卑,并且期待在這兒找到它的值翘紊。 再強調(diào)一下, "".~r2 并沒有具體的文法含義藐唠。

為了更好地延時go語言是如何處理多返回值的情況帆疟,我們也返回了一個固定的布爾類型的true. 機制跟返回第一個值是一樣的,只是距離SP的偏移量變了宇立。

0x0013 RET

最后的偽指令 RET 告訴Go編譯器插入一些指令踪宠,這些指令依托于特定的平臺,以便從子協(xié)程調(diào)用中返回妈嘹。
像這樣的操作會將存儲在0(SP)中的返回地址彈出柳琢,并且跳回到這個地址。

一個TEXT塊的最后一條指令必須是jump指令的一種,通常是一個RET(偽)指令柬脸。(如果不是他去,鏈接器會自己加入一個”跳回自己”的指令;TEXTs中沒有fallthrough)

這兒需要一次性學(xué)習(xí)好多句法和語言倒堕,這兒有一個快速的總結(jié):

聲明一個全局方法  "".add (實際鏈接是 main.add )
;;  不要插入stack-split 序列
;; 0 字節(jié) 椩植猓空間,16字節(jié)傳入?yún)?shù) 
;; func add(a, b int32) (int32, bool)
0x0000 TEXT "".add(SB), NOSPLIT, $0-16
  ;; ...省略 FUNCDATA 相關(guān)知識...
  0x0000 MOVL   "".b+12(SP), AX     ;; move second Long-word (4B) argument from caller's stack-frame into AX
  0x0004 MOVL   "".a+8(SP), CX      ;; move first Long-word (4B) argument from caller's stack-frame into CX
  0x0008 ADDL   CX, AX          ;; 計算 AX=CX+AX
  0x000a MOVL   AX, "".~r2+16(SP)   ;; 把結(jié)果移動到調(diào)用者的椏寻停空間 
  0x000e MOVB   $1, "".~r3+20(SP)   ;; 將布爾類型的true移動到調(diào)用者的椣碧拢空間
  0x0013 RET                ;; 跳轉(zhuǎn)到 0(SP)中存儲地址對應(yīng)內(nèi)存中去 

最后,這兒有一個當(dāng)main.add 執(zhí)行完之后棧的示意圖

   |    +-------------------------+ <-- 32(SP)              
   |    |                         |                         
 G |    |                         |                         
 R |    |                         |                         
 O |    | main.main's saved       |                         
 W |    |     frame-pointer (BP)  |                         
 S |    |-------------------------| <-- 24(SP)              
   |    |      [alignment]        |                         
 D |    | "".~r3 (bool) = 1/true  | <-- 21(SP)              
 O |    |-------------------------| <-- 20(SP)              
 W |    |                         |                         
 N |    | "".~r2 (int32) = 42     |                         
 W |    |-------------------------| <-- 16(SP)              
 A |    |                         |                         
 R |    | "".b (int32) = 32       |                         
 D |    |-------------------------| <-- 12(SP)              
 S |    |                         |                         
   |    | "".a (int32) = 10       |                         
   |    |-------------------------| <-- 8(SP)               
   |    |                         |                         
   |    |                         |                         
   |    |                         |                         
 \ | /  | return address to       |                         
  \|/   |     main.main + 0x30    |                         
   -    +-------------------------+ <-- 0(SP) (TOP OF STACK)

(diagram made with https://textik.com)

分析 main

省略了一些代碼骤宣,幫你節(jié)省滾鼠標(biāo)的時間秦爆,如下是main方法匯編之后的樣子:

0x0000 TEXT     "".main(SB), $24-0
  ;; ...omitted stack-split prologue...
  0x000f SUBQ       $24, SP
  0x0013 MOVQ       BP, 16(SP)
  0x0018 LEAQ       16(SP), BP
  ;; ...omitted FUNCDATA stuff...
  0x001d MOVQ       $137438953482, AX
  0x0027 MOVQ       AX, (SP)
  ;; ...omitted PCDATA stuff...
  0x002b CALL       "".add(SB)
  0x0030 MOVQ       16(SP), BP
  0x0035 ADDQ       $24, SP
  0x0039 RET
  ;; ...omitted stack-split epilogue...
0x0000 TEXT "".main(SB), $24-0

其實也沒啥:

  • "".main (鏈接之后是main.main)在.text 代碼段中中是一個全局方法,它有一個到我們地址空間開始處有固定偏移量的地址憔披。
  • 分配了24字節(jié)的椀认蓿空間 并且沒有接收任何參數(shù),也沒有任何返回活逆。
0x000f SUBQ     $24, SP
0x0013 MOVQ     BP, 16(SP)
0x0018 LEAQ     16(SP), BP

如上文所述精刷,Go 調(diào)用必須將所有的參數(shù)放在棧中。
main這個調(diào)用者蔗候,通過遞減虛擬棧指針怒允,增加了24字節(jié)的棧空間(記仔庖!:棧是向下增長的纫事,所以SUBQ 指令就是擴大棧空間)所灸。這24字節(jié)包含如下部分:

  • 8字節(jié)(16(SP) - 24(SP))用來存儲當(dāng)前棧指針BP的值丽惶,為了 棧展開(stack-unwinding) 和 方便調(diào)試(facilitate debugging)。
  • 1+3 字節(jié) (12(SP) - 16(SP)) 為了存儲第二個返回值(布爾類型)加 3字節(jié)的偏移對齊量(amd64 機器架構(gòu))
  • 4字節(jié)(8(SP) - 12(SP))存儲第一個返回值(int32)
  • 4字節(jié)(4(SP) - 8(SP))存儲參數(shù)b(int32)
  • 4字節(jié)(0(SP) - 4(SP))存儲參數(shù)a(int32)

最后爬立, 伴隨著棧的增長钾唬,LEAQ 計算棧指針的新地址,并將其存儲在BP中侠驯。

0x001d MOVQ     $137438953482, AX
0x0027 MOVQ     AX, (SP)

調(diào)用者把給 被調(diào)用者的參數(shù)作為一個Quad word 推送到剛剛擴容棧的棧頂抡秆。
137438953482 雖然開始看起來像個垃圾值,實際上這個值對應(yīng)的就是 1032 這兩個 4 字節(jié)值吟策,它們兩被連接成了一個 8 字節(jié)值儒士。

$ echo 'obase=2;137438953482' | bc
10000000000000000000000000000000001010
\____/\______________________________/
   32                              10
0x002b CALL     "".add(SB)

我們使用相對于基地址指針的偏移量來調(diào)用add方法, 這相當(dāng)于直接跳到一個指定的地址檩坚。
注意:CALL 將返回地址(一個8字節(jié)的值)推到棧頂着撩;所以每次在add函數(shù)中引用SP寄存器的時候還需要額外偏移8字節(jié)诅福。
比如 "".a 不再位于0(SP), 而是位于 8(SP).

0x0030 MOVQ     16(SP), BP
0x0035 ADDQ     $24, SP
0x0039 RET

最后:
1、將幀指針(frame-pointer)下降一個棧幀(stack-frame)的大小(就是“向下”一級)
2拖叙、釋放我們先前占用的24字節(jié)椕ト螅空間
3、請求Go匯編器插入一個子協(xié)程返回相關(guān)的操作

分析goroutines, stacks 和 splits

現(xiàn)在并不是一個深入研究go協(xié)程實現(xiàn)的好時機(后面可能會講)薯鳍,但是隨著我們越來越多地研究匯編輸出旺芽, 跟棧管理相關(guān)的指令將會迅速熟悉起來。我們應(yīng)該快速熟悉這些模式辐啄,當(dāng)我們熟悉了,我們會理解指令在做什么以及為什么這么做运嗜。

棧(Stacks)

由于Go程序中的協(xié)程數(shù)量是不確定的壶辜,在實踐中會有幾百萬個,所以在分配椀W猓空間時要保守一些砸民,避免耗光所有可用的內(nèi)存。
每個協(xié)程啟動時會分配2kb的棧內(nèi)存(雖然說是椃芫龋空間岭参,其實分配在堆上).
當(dāng)協(xié)程執(zhí)行其job時,可能因為超出其初始分配的空間大小尝艘。為了避免這種情況發(fā)生演侯,runtime 保證當(dāng)一個協(xié)程超出其棧空間時背亥,會分配一個2倍大小的空間給它秒际,并將初始空間的內(nèi)容分配到新空間中。
這個過程被稱為棧分裂(stack-split) 狡汉,這是協(xié)程棧動態(tài)大小的一個有效方法娄徊。

分裂(Splits)

為了保證stack-splitting 正常工作,編譯器會在可能發(fā)生棧超出的每個方法開始跟結(jié)束插入幾條指令盾戴。
正如在本文開始我們看到的寄锐,為了避免沒必要的超出,被標(biāo)注NOSPLIT的方法不會擴大他的椉夥龋空間橄仆。有了這個標(biāo)注,編譯器不會插入這些指令可婶。
看一下main方法沿癞,這次沒有省略 stack-split 前導(dǎo)指令:

0x0000 TEXT "".main(SB), $24-0
  ;; stack-split prologue
  0x0000 MOVQ   (TLS), CX
  0x0009 CMPQ   SP, 16(CX)
  0x000d JLS    58

  0x000f SUBQ   $24, SP
  0x0013 MOVQ   BP, 16(SP)
  0x0018 LEAQ   16(SP), BP
  ;; ...omitted FUNCDATA stuff...
  0x001d MOVQ   $137438953482, AX
  0x0027 MOVQ   AX, (SP)
  ;; ...omitted PCDATA stuff...
  0x002b CALL   "".add(SB)
  0x0030 MOVQ   16(SP), BP
  0x0035 ADDQ   $24, SP
  0x0039 RET

  ;; stack-split epilogue
  0x003a NOP
  ;; ...omitted PCDATA stuff...
  0x003a CALL   runtime.morestack_noctxt(SB)
  0x003f JMP    0

如我們所見,stack-split 序列被分成一個prologue (序幕) 和一個epilogue(結(jié)束):

  • prologue 檢查協(xié)程是否超出空間矛渴,如果是椎扬,直接跳到epilogue
  • epilogue 惫搏,會觸發(fā)棧增長機制,然后跳轉(zhuǎn)到prologue

這創(chuàng)造了一個反饋循環(huán)蚕涤,這個循環(huán)保證了只要有足夠的椏鹋猓空間未被分配,“饑餓"協(xié)程就能正常運轉(zhuǎn)揖铜。

Prologue
0x0000 MOVQ (TLS), CX   ;; store current *g in CX
0x0009 CMPQ SP, 16(CX)  ;; compare SP and g.stackguard0
0x000d JLS  58      ;; jumps to 0x3a if SP <= g.stackguard0

TLS 是一個runtime線程持有的虛擬寄存器茴丰,保存了指向當(dāng)前go協(xié)程的指針。會跟蹤當(dāng)前協(xié)程的運行時狀態(tài).
看一下runtime中g(shù)協(xié)程的定義

type g struct {
    stack       stack   // 16 bytes
    // stackguard0 is the stack pointer compared in the Go stack growth prologue.
    // It is stack.lo+StackGuard normally, but can be StackPreempt to trigger a preemption.
    stackguard0 uintptr
    stackguard1 uintptr

    // ...omitted dozens of fields...
}

16(CX) 關(guān)聯(lián)了 g.stackguard0天吓,一個閾值贿肩。其用途是當(dāng)跟棧指針比較,判斷一個協(xié)程是否馬上要用完當(dāng)前的椓淠空間汰规。
prologue 檢查當(dāng)前SP的值是否小于或者等于stackguard0 的值,如果是物邑,則跳轉(zhuǎn)到Epilogue溜哮。

Epilogue
0x003a NOP
0x003a CALL runtime.morestack_noctxt(SB)
0x003f JMP  0

Epilogue 的結(jié)構(gòu)比較直接: 它直接調(diào)用runtime的函數(shù),然后跳轉(zhuǎn)到方法的第一個指令prologue去色解。

在 CALL 之前出現(xiàn)的 NOP 這個指令使 prologue 部分不會直接跳到 CALL 指令位置茂嗓。在一些平臺上,直接跳到 CALL 可能會有一些麻煩的問題科阎;所以在調(diào)用位置插一個 noop 的指令并在跳轉(zhuǎn)時跳到這個 NOP 位置是一種最佳實踐述吸。
[更新:我們在issue #4: Clarify "nop before call" paragraph.討論了這個問題]

Minus some subtleties (缺失了一些細節(jié))

我們僅僅覆蓋了冰山一角。
像棧擴容有很多細節(jié)我們在這兒并不能一一描述锣笨。擴容細節(jié)非常復(fù)雜刚梭,將會有一個單獨的章節(jié)介紹。

到時再回頭看這兒票唆。

總結(jié)

快速介紹GO匯編 已經(jīng)給你足夠的內(nèi)容去玩味朴读。
隨著我們深入研究本書其他部分的go內(nèi)核知識,Go匯編將會是我們理解場景背后知識最依賴的工具之一走趋。

如果有任何問題或者建議衅金,不要遲疑,創(chuàng)建一個有關(guān)chapter1的issue簿煌, prefix!!!

參考文獻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末氮唯,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姨伟,更是在濱河造成了極大的恐慌惩琉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夺荒,死亡現(xiàn)場離奇詭異瞒渠,居然都是意外死亡良蒸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門伍玖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫩痰,“玉大人,你說我怎么就攤上這事窍箍〈模” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵椰棘,是天一觀的道長纺棺。 經(jīng)常有香客問我,道長邪狞,這世上最難降的妖魔是什么五辽? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮外恕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘乡翅。我一直安慰自己鳞疲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布蠕蚜。 她就那樣靜靜地躺著尚洽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪靶累。 梳的紋絲不亂的頭發(fā)上腺毫,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音羊瘩,去河邊找鬼攘乒。 笑死舞箍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的急黎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼侧到,長吁一口氣:“原來是場噩夢啊……” “哼勃教!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匠抗,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤故源,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后汞贸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绳军,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡印机,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了删铃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耳贬。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猎唁,靈堂內(nèi)的尸體忽然破棺而出咒劲,到底是詐尸還是另有隱情,我是刑警寧澤诫隅,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布腐魂,位于F島的核電站,受9級特大地震影響逐纬,放射性物質(zhì)發(fā)生泄漏蛔屹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一豁生、第九天 我趴在偏房一處隱蔽的房頂上張望兔毒。 院中可真熱鬧,春花似錦甸箱、人聲如沸育叁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豪嗽。三九已至,卻和暖如春豌骏,著一層夾襖步出監(jiān)牢的瞬間龟梦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工窃躲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留计贰,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓蒂窒,卻偏偏與公主長得像蹦玫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子刘绣,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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

  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用樱溉。當(dāng)一個函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,588評論 1 19
  • 王爽匯編全書知識點大綱 第一章 基礎(chǔ)知識 機器語言 匯編語言的產(chǎn)生 匯編語言的組成 存儲器 cpu對存儲器的讀寫 ...
    2c3ba901516f閱讀 2,407評論 0 1
  • 編程語言的發(fā)展 機器語言由0和1組成 匯編語言(Assembly Language)用符號代替了0和1,比機器語言...
    阿凡提說AI閱讀 3,979評論 0 15
  • 1.地址總線纬凤,數(shù)據(jù)總線福贞,控制總線在哪里,它們有什么作用?答:它們都是cpu連接外部組件的線路停士。地址總線:地址總線A...
    MagicalGuy閱讀 1,428評論 0 1