Makefile使用(學習筆記)

makefile的規則

組成

  • target
  • prerequisites
  • command
    target這一個或多個的目標文件依賴于prerequisites中的文件,其生成規則定義在command中。說白一點就是說,prerequisites中如果有一個以上的文件比target文件要新的話,command所定義的命令就會被執行。這就是Makefile的規則。也就是Makefile中最核心的內容

文件名

“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通過make -f或者make --file來指定文件)

makefile工作流程

  1.    讀入所有的Makefile。
    
  2.    讀入被include的其它Makefile。
    
  3.    初始化文件中的變量。
    
  4.    推導隱晦規則,并分析所有規則。
    
  5.    為所有的目標文件創建依賴關系鏈。
    
  6.    根據依賴關系,決定哪些目標要重新生成。
    
  7.    執行生成命令。
    

編譯多個c文件過程例子
在默認的方式下,也就是我們只輸入make命令。那么,

  • make會在當前目錄下找名字叫“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通過make -f或者make --file來指定文件)。
  • 如果找到,它會找文件中的第一個目標文件(target),在上面的例子中,他會找到“edit”這個文件,并把這個文件作為最終的目標文件。
  • 如果edit文件不存在,或是edit所依賴的后面的 .o 文件的文件修改時間要比edit這個文件新,那么,他就會執行后面所定義的命令來生成edit這個文件。
  • 如果edit所依賴的.o文件也存在,那么make會在當前文件中找目標為.o文件的依賴性,如果找到則再根據那一個規則生成.o文件。(這有點像一個堆棧的過程)
  • 當然,你的C文件和H文件是存在的啦,于是make會生成 .o 文件,然后再用 .o 文件聲明make的終極任務,也就是執行文件edit了。
    這就是整個make的依賴性,make會一層又一層地去找文件的依賴關系,直到最終編譯出第一個目標文件。在找尋的過程中,如果出現錯誤,比如最后被依賴的文件找不到,那么make就會直接退出,并報錯,而對于所定義的命令的錯誤,或是編譯不成功,make根本不理。make只管文件的依賴性,即,如果在我找了依賴關系之后,冒號后面的文件還是不在,那么對不起,我就不工作啦。
    通過上述分析,我們知道,像clean這種,沒有被第一個目標文件直接或間接關聯,那么它后面所定義的命令將不會被自動執行,不過,我們可以顯示要make執行。即命令——“make clean”,以此來清除所有的目標文件,以便重編譯。
    于是在我們編程中,如果這個工程已被編譯過了,當我們修改了其中一個源文件,比如file.c,那么根據我們的依賴性,我們的目標file.o會被重編譯(也就是在這個依性關系后面所定義的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改時間要比edit要新,所以edit也會被重新鏈接了(詳見edit目標文件后定義的命令)。
    而如果我們改變了“command.h”,那么,kdb.o、command.o和files.o都會被重編譯,并且,edit會被重鏈接。

語法

基本格式

target:prerequisites
    command

或者

target:prerequisites;command

command 前面是tab鍵,不是空格,可以用\來分行,target、prerequisite、command都可以多個用空格隔開,后面的command要用前一條的結果需要使用分號隔開,比如

cd /home;pwd

cd home
pwd

是不同的,command可以多行

注釋

只支持行注釋,用#

默認target

只輸入make,默認執行第一個target,也可以指定

default: modules

變量

VAR=......
$(VAR)
  • 大小寫敏感
  • 要使用$時,用$$,不是\$
  • 除了用=,還可以用:=,這種方法,前面的變量不能使用后面的變量,只能使用前面已定義好了的變量
x := foo
y := $(x) bar
x := later

等價于

y := foo bar
x := later

如果使用定義在后面的變量,則沒有值

y := $(x) bar
x := foo

y=bar,x=foo

  • ?=:沒有定義這個變量就定義一個
  • $(var.a=b):替換 變量中的值
foo := a.o b.o c.o
bar := $(foo:.o=.c)
  • 變量的值當成變量
x = y
y = z
a := $($(x))
  • 變量組合
first_second = Hello
a = first
b = second
all = $($a_$b)
  • 追加變量值
objects = main.o foo.o bar.o utils.o
objects += another.o
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
  • 目標變量(Target-specific Variable)
    只在這個目標內生效(類似C局部變量)
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
  • 模式變量(Pattern-specific Variable)
    make的“模式”一般是至少含有一個“%”的,所以,我們可以以如下方式給所有以[.o]結尾的目標定義目標變量:
%.o : CFLAGS = -O

同樣,模式變量的語法和“目標變量”一樣:

<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>

override同樣是針對于系統環境傳入的變量,或是make命令行指定的變量。

  • 自動化變量
    自動化變量,就是這種變量會把模式中所定義的一系列的文件自動地挨個取出,直至所有的符合模式的文件都取完了。這種自動化變量只應出現在規則的命令中。

override指示符

如果有變量是通常make的命令行參數設置的,那么Makefile中對這個變量的賦值會被忽略。如果你想在Makefile中設置這類參數的值,那么,你可以使用“override”指示符。其語法是:

override <variable> = <value>
override <variable> := <value>
當然,還可以追加:
override <variable> += <more text>
多行變量:
override define foo
bar
endef

.PHONY(偽目標)

target相同名的文件在makefile目錄下,執行target而不是

.PHONY: modules
modules:

include

include<filename>
  • filename可以是當前操作系統Shell的文件模式(可以保含路徑和通配符)
  • 在include前面可以有一些空字符,但是絕不能是[Tab]鍵開始。include和可以用一個或多個空格隔開。

忽略警告繼續執行

clean:
   -rm not_exist.file exist.file

-include<filename>

環境變量MAKEFILES

所有makefile都會include它,影響到所有makefile,不建議使用

通配符

  • ~:當前用戶
  • *
  • ?

轉義符、單行拆分成多行

clean:
   VAR=find ./ -name "a*"
   echo $(VAR)
   rm a &&\
   touch b

vpath

Makefile文件中的特殊變量“VPATH”就是完成這個功能的,如果沒有指明這個變量,make只會在當前的目錄中去找尋依賴文件和目標文件。如果定義了這個變量,那么,make就會在當當前目錄找不到的情況下,到所指定的目錄中去找尋文件了。

VPATH = src:../headers

上面的的定義指定兩個目錄,“src”和“../headers”,make會按照這個順序進行搜索。目錄由“冒號”分隔。(當然,當前目錄永遠是最高優先搜索的地方)

另一個設置文件搜索路徑的方法是使用make的“vpath”關鍵字(注意,它是全小寫的),這不是變量,這是一個make的關鍵字,這和上面提到的那個VPATH變量很類似,但是它更為靈活。它可以指定不同的文件在不同的搜索目錄中。這是一個很靈活的功能。它的使用方法有三種:

  1.    vpath < pattern> < directories>    為符合模式< pattern>的文件指定搜索目錄<directories>。
    
  2.    vpath < pattern>                              清除符合模式< pattern>的文件的搜索目錄。
    
  3.    vpath                                                 清除所有已被設置好了的文件搜索目錄。
    

vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”結尾的文件。< pattern>指定了要搜索的文件集,而< directories>則指定了的文件集的搜索的目錄。例如:
vpath %.h ../headers
可以連續地使用vpath語句,以指定不同搜索策略

   vpath %.c foo
   vpath %   blish
   vpath %.c bar

多target

$@:目前規則中所有的目標集合

   bigoutput littleoutput : text.g
           generate text.g -$(subst output,,$@) > $@

上述規則等價于:

  bigoutput : text.g
          generate text.g -big > bigoutput
  littleoutput : text.g
          generate text.g -little > littleoutput

其中,-(subst output,,@)中的“”表示執行一個Makefile的函數,函數名為subst,后面的為參數。關于函數,將在后面講述。這里的這個函數是截取字符串的意思,“@”表示目標的集合,就像一個數組,“$@”依次取出目標,并執于命令。

靜態模式

   objects = foo.o bar.o
 
   all: $(objects)
 
   $(objects): %.o: %.c
           $(CC) -c $(CFLAGS) $< -o $@

自動生成依賴性

cc -M main.c

其輸出是:

main.o : main.c defs.h

注意gcc

gcc -M main.c

相當于

 main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
        /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
        /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
        /usr/include/bits/sched.h /usr/include/libio.h \
        /usr/include/_G_config.h /usr/include/wchar.h \
        /usr/include/bits/wchar.h /usr/include/gconv.h \
        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
        /usr/include/bits/stdio_lim.h
gcc-MM main.c

相當于

main.o: main.c defs.h

系統命令

可以使用系統的命令,默認使用/bin/sh

命令顯示(回顯)

在命令前家@可以不顯示執行的命令

@echo 'hello'

不會輸出echo `hello`,只輸出hello
使用make -n(或者--just-print),只顯示過程,真正執行命令,用來調試makefile

嵌套makefile

subsystem:
 cd subdir && $(MAKE)

傳遞變量:export <variable>
傳遞所有變量:export
始終會默認傳遞的變量:MAKEFLAGS,SHELL
記錄嵌套層數的變量:MAKELEVEL

定義命令包(多行變量)

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

foo.c : foo.y
    $(run-yacc)

命令包“run-yacc”中的“^”就是“foo.y”,“@”就是“foo.c”

條件判斷

ifeq(,) #ifeq ' ' ' '  或者ifeq " " " "或ifeq ' ' " "
...
else
...
endif

ifneq ( , )
...
endif

ifdef <...>
...
endif

函數

$開頭,參數之間用,隔開

$(<function> <arguments> )
或是
${<function> <arguments>}
  • 字符串操作函數
$(subst <from>,<to>,<text> )
名稱:字符串替換函數——subst。
功能:把字串<text>中的<from>字符串替換成<to>。
返回:函數返回被替換過后的字符串。
$(patsubst <pattern>,<replacement>,<text> )
名稱:模式字符串替換函數——patsubst。
功能:查找<text>中的單詞(單詞以“空格”、“Tab”或“回車”“換行”分隔)是否符合模式<pattern>,如果匹配的話,則以<replacement>替換。這里,<pattern>可以包括通配符“%”,表示任意長度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的這個“%”將是<pattern>中的那個“%”所代表的字串。(可以用“\”來轉義,以“\%”來表示真實含義的“%”字符)返回:函數返回被替換過后的字符串。
$(strip <string> )


名稱:去空格函數——strip。
功能:去掉<string>字串中開頭和結尾的空字符。
返回:返回被去掉空格的字符串值。
$(findstring <find>,<in> )


名稱:查找字符串函數——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否則返回空字符串。
$(filter <pattern...>,<text> )


名稱:過濾函數——filter。
功能:以<pattern>模式過濾<text>字符串中的單詞,保留符合模式<pattern>的單詞。可
以有多個模式。
返回:返回符合模式<pattern>的字串。
示例:


sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter-out <pattern...>,<text> )


名稱:反過濾函數——filter-out。
功能:以<pattern>模式過濾<text>字符串中的單詞,去除符合模式<pattern>的單詞。可
以有多個模式。
返回:返回不符合模式<pattern>的字串。
$(sort <list> )


名稱:排序函數——sort。
功能:給字符串<list>中的單詞排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
備注:sort函數會去掉<list>中相同的單詞。
$(word <n>,<text> )


名稱:取單詞函數——word。
功能:取字符串<text>中第<n>個單詞。(從一開始)
返回:返回字符串<text>中第<n>個單詞。如果<n>比<text>中的單詞數要大,那么返回空
字符串。
$(wordlist <s>,<e>,<text> )


名稱:取單詞串函數——wordlist。
功能:從字符串<text>中取從<s>開始到<e>的單詞串。<s>和<e>是一個數字。
返回:返回字符串<text>中從<s>到<e>的單詞字串。如果<s>比<text>中的單詞數要大,那
么返回空字符串。如果<e>大于<text>的單詞數,那么返回從<s>開始,到<text>結束的單
詞串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。
$(words <text> )


名稱:單詞個數統計函數——words。
功能:統計<text>中字符串中的單詞個數。
返回:返回<text>中的單詞數。
示例:$(words, foo bar baz)返回值是“3”。
備注:如果我們要取<text>中最后的一個單詞,我們可以這樣:$(word $(words <text> 
),<text> )。
$(firstword <text> )


名稱:首單詞函數——firstword。
功能:取字符串<text>中的第一個單詞。
返回:返回字符串<text>的第一個單詞。
示例:$(firstword foo bar)返回值是“foo”。
備注:這個函數可以用word函數來實現:$(word 1,<text> )。
  • 文件名操作函數
$(dir <names...> )


名稱:取目錄函數——dir。
功能:從文件名序列<names>中取出目錄部分。目錄部分是指最后一個反斜杠(“/”)之
前的部分。如果沒有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目錄部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
$(notdir <names...> )


名稱:取文件函數——notdir。
功能:從文件名序列<names>中取出非目錄部分。非目錄部分是指最后一個反斜杠(“/”
)之后的部分。
返回:返回文件名序列<names>的非目錄部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
$(suffix <names...> )


名稱:取后綴函數——suffix。
功能:從文件名序列<names>中取出各個文件名的后綴。
返回:返回文件名序列<names>的后綴序列,如果文件沒有后綴,則返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。
$(basename <names...> )


名稱:取前綴函數——basename。
功能:從文件名序列<names>中取出各個文件名的前綴部分。
返回:返回文件名序列<names>的前綴序列,如果文件沒有前綴,則返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar h
acks”。
$(addsuffix <suffix>,<names...> )


名稱:加后綴函數——addsuffix。
功能:把后綴<suffix>加到<names>中的每個單詞后面。
返回:返回加過后綴的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
$(addprefix <prefix>,<names...> )


名稱:加前綴函數——addprefix。
功能:把前綴<prefix>加到<names>中的每個單詞后面。
返回:返回加過前綴的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
$(join <list1>,<list2> )


名稱:連接函數——join。
功能:把<list2>中的單詞對應地加到<list1>的單詞后面。如果<list1>的單詞個數要比<
list2>的多,那么,<list1>中的多出來的單詞將保持原樣。如果<list2>的單詞個數要比
<list1>多,那么,<list2>多出來的單詞將被復制到<list2>中。
返回:返回連接過后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。

foreach函數

$(foreach <var>,<list>,<text> )
names := a b c d


files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的單詞會被挨個取出,并存到變量“n”中,“$(n).o”每次根據“$(n)”計算出一個值,這些值以空格分隔,最后作為foreach函數的返回,所以,$(f
iles)的值是“a.o b.o c.o d.o”。


注意,foreach中的<var>參數是一個臨時的局部變量,foreach函數執行完后,參數<var>的變量將不在作用,其作用域只在foreach函數當中。
  • if函數
$(if <condition>,<then-part> )

或是

$(if <condition>,<then-part>,<else-part> )
  • call函數
    call函數是唯一一個可以用來創建新的參數化的函數。你可以寫一個非常復雜的表達式,這個表達式中,你可以定義許多參數,然后你可以用call函數來向這個表達式傳遞參數。其語法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
reverse = $(1) $(2)
foo = $(call reverse,a,b)
(foo值:a b)
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此時的foo的值就是“b a”。
  • origin函數
    origin函數不像其它的函數,他并不操作變量的值,他只是告訴你你的這個變量是哪里來的?其語法是:
$(origin <variable> )

取值:

“undefined”


如果<variable>從來沒有定義過,origin函數返回這個值“undefined”。

“default”


如果<variable>是一個默認的定義,比如“CC”這個變量,這種變量我們將在后面講述。

“environment”


如果<variable>是一個環境變量,并且當Makefile被執行時,“-e”參數沒有被打開。


“file”


如果<variable>這個變量被定義在Makefile中。


“command line”


如果<variable>這個變量是被命令行定義的。

“override”


如果<variable>是被override指示符重新定義的。

“automatic”


如果<variable>是一個命令運行中的自動化變量。關于自動化變量將在后面講述。
  • shell 函數
    shell 函數也不像其它的函數。顧名思義,它的參數應該就是操作系統Shell的命令。它和反引號“`”是相同的功能。這就是說,shell函數把執行操作系統命令后的輸出作為函數
    返回。于是,我們可以用操作系統命令以及字符串處理命令awk,sed等等命令來生成一個變量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)

! 注意,這個函數會新生成一個Shell程序來執行命令,所以你要注意其運行性能,如果你的Makefile中有一些比較復雜的規則,并大量使用了這個函數,那么對于你的系統性能是有害的。特別是Makefile的隱晦的規則可能會讓你的shell函數執行的次數比你想像的多得多。

  • 控制make的函數
$(error <text ...> )
$(warning <text ...> )

make退出碼

0 —— 表示成功執行。
1 —— 如果make運行時出現任何錯誤,其返回1。
2 —— 如果你使用了make的“-q”選項,并且make使得一些目標不需要更新,那么返回2。

make指定目標

GNU這種開源軟件的發布時,其 makefile都包含了編譯、安裝、打包等功能。我們可以參照這種規則來書寫我們的makefile中的目標。
**“all” ** 這個偽目標是所有目標的目標,其功能一般是編譯所有的目標。
**“clean” **這個偽目標功能是刪除所有被make創建的文件。
**“install” **這個偽目標功能是安裝已編譯好的程序,其實就是把目標執行文件拷貝到指定的目標中去。
**“print” **這個偽目標的功能是例出改變過的源文件。
**“tar” ** 這個偽目標功能是把源程序打包備份。也就是一個tar文件。
**“dist” ** 這個偽目標功能是創建一個壓縮文件,一般是把tar文件壓成Z文件。或是gz文件。
**“TAGS” ** 這個偽目標功能是更新所有的目標,以備完整地重編譯使用。
**“check”和“test” **這兩個偽目標一般用來測試makefile的流程。

make檢查規則 make調試

  • make時加參數
有時候,我們不想讓我們的makefile中的規則執行起來,我們只想檢查一下我們的命令,或是執行的序列。于是我們可以使用make命令的下述參數:


“-n”
“--just-print”
“--dry-run”
“--recon”
不執行參數,這些參數只是打印命令,不管目標是否更新,把規則和連帶規則下的命令打印出來,但不執行,這些參數對于我們調試makefile很有用處。


“-t”
“--touch”
這個參數的意思就是把目標文件的時間更新,但不更改目標文件。也就是說,make假裝編譯目標,但不是真正的編譯目標,只是把目標變成已編譯過的狀態。


“-q”
“--question”
這個參數的行為是找目標的意思,也就是說,如果目標存在,那么其什么也不會輸出,當然也不會執行編譯,如果目標不存在,其會打印出一條出錯信息。


“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
這個參數需要指定一個文件。一般是是源文件(或依賴文件),Make會根據規則推導來運行依賴于這個文件的命令,一般來說,可以和“-n”參數一同使用,來查看這個依賴文件
所發生的規則命令。

另外一個很有意思的用法是結合“-p”和“-v”來輸出makefile被執行時的信息(這個將在后面講述)。
  • 在makefile中添加調試信息
    使用
 $(`信息等級` `...調試信息..`)
如:
$(info "hello")
$(warning "hello")
$(error "hello")

或者使用

@echo ......

這種的局限就是只能在目標后面使用

make參數

參見make --help或者man make

隱含規則

報錯立即停止

參考:makefile出現錯誤卻不停止,卻繼續運行

makefile執行錯誤,結果還會繼續執行,此處是由于是上層makefile調用下層子makefile,子makefile執行出錯,停止返回到上層后,上層沒有判斷返回值,導致還是會繼續執行。

解決辦法是,對于子makefile調用,判斷返回值,
比如將:
make $@;
改為:
make $@ || exit "$$?";
這樣make執行錯誤返回值為非0,然后就可以執行后面的exit而退出了。

==================================

學習資料

Makefile經典教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容