C語言探索之旅 | 第二部分第八課:動態(tài)分配

作者 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)。
轉(zhuǎn)載請注明出處。
原文:http://www.lxweimin.com/p/bbce8f04faf1

《C語言探索之旅》全系列

內(nèi)容簡介


  1. 前言
  2. 變量的大小
  3. 內(nèi)存的動態(tài)分配
  4. 動態(tài)分配一個數(shù)組
  5. 總結(jié)
  6. 第二部分第九課預(yù)告

1. 前言


上一課是 C語言探索之旅 | 第二部分第七課:文件讀寫

經(jīng)歷了第二部分的一些難點課程,我們終于來到了這一課,一個聽起來有點酷酷的名字:動態(tài)分配

“萬水千山總是情,分配也由系統(tǒng)定”。

到目前為止,我們創(chuàng)建的變量都是系統(tǒng)的編譯器為我們自動構(gòu)建的,這是簡單的方式。

其實還有一種更偏手動的創(chuàng)建變量的方式,我們稱為“動態(tài)分配”(Dynamic Allocation)。dynamic 表示“動態(tài)的”,allocation 表示“分配”。

動態(tài)分配的一個主要好處就是可以在內(nèi)存中“預(yù)置”一定空間大小,在編譯時還不知道到底會用多少。

使用這個技術(shù),我們可以創(chuàng)建大小可變的數(shù)組。到目前為止我們所創(chuàng)建的數(shù)組都是大小固定不可變的。而學(xué)完這一課后我們就會創(chuàng)建所謂“動態(tài)數(shù)組”了。

學(xué)習(xí)這一章需要對指針有一定了解,如果指針的概念你還沒掌握好,可以回去復(fù)習(xí) C語言探索之旅 | 第二部分第二課:進(jìn)擊的指針,C語言的王牌! 那一課。

我們知道當(dāng)我們創(chuàng)建一個變量時,在內(nèi)存中要為其分配一定大小的空間。例如:

int number = 2;

當(dāng)程序運行到這一行代碼時,會發(fā)生幾件事情:

  1. 應(yīng)用程序詢問操作系統(tǒng)(Operating System,簡稱 OS。例如Windows,Linux,macOS,Android,iOS,等)是否可以使用一小塊內(nèi)存空間。

  2. 操作系統(tǒng)回復(fù)我們的程序,告訴它可以將這個變量存儲在內(nèi)存中哪個地方(給出分配的內(nèi)存地址)。

  3. 當(dāng)函數(shù)結(jié)束后,你的變量會自動從內(nèi)存中被刪除。你的程序?qū)Σ僮飨到y(tǒng)說:“我已經(jīng)不需要內(nèi)存中的這塊地址了,謝謝!” (當(dāng)然,實際上你的程序不可能對操作系統(tǒng)說一聲“謝謝”,但是確實是操作系統(tǒng)在掌管一切,包括內(nèi)存,所以對它還是客氣一點比較好...)。

可以看到,以上的過程都是自動的。當(dāng)我們創(chuàng)建一個變量,操作系統(tǒng)就會自動被程序這樣調(diào)用。

那么什么是手動的方式呢?說實在的,沒人喜歡把事情復(fù)雜化,如果自動方式可行,何必要大費周章來使用什么手動方式呢?但是要知道,很多時候我們是不得不使用手動方式。

這一課中,我們將會:

  1. 探究內(nèi)存的機制(是的,雖然以前的課研究過,但是還是要繼續(xù)深入),了解不同變量類型所占用的內(nèi)存大小。

  2. 接著,探究這一課的主題,來學(xué)習(xí)如何向操作系統(tǒng)動態(tài)請求內(nèi)存。也就是所謂的“動態(tài)內(nèi)存分配”。

  3. 最后,通過學(xué)習(xí)如何創(chuàng)建一個在編譯時還不知道其大小(只有在程序運行時才知道)的數(shù)組來了解動態(tài)內(nèi)存分配的好處。

準(zhǔn)備好了嗎?Let's Go !

2. 變量的大小


根據(jù)我們所要創(chuàng)建的變量的類型(char,int,double,等等),其所占的內(nèi)存空間大小是不一樣的。

事實上,為了存儲一個大小在 -128 至 127 之間的數(shù)(char 類型),只需要占用一個字節(jié)(8 個二進(jìn)制位)的內(nèi)存空間,是很小的。

然而,一個 int 類型的變量就要占據(jù) 4 個字節(jié)了;一個 double 類型要占據(jù) 8 個字節(jié)。

問題是:并不總是這樣。

什么意思呢?

因為類型所占內(nèi)存的大小還與操作系統(tǒng)有關(guān)系。不同的操作系統(tǒng)可能就不一樣,32 位和 64 位的操作系統(tǒng)的類型大小一般會有區(qū)別。

這一節(jié)中我們的目的是學(xué)習(xí)如何獲知變量所占用的內(nèi)存大小。

有一個很簡單的方法:使用 sizeof()

雖然看著有點像函數(shù),但其實 sizeof 不是一個函數(shù),而是一個 C語言的關(guān)鍵字,也算是一個運算符吧。

我們只需要在 sizeof 的括號里填入想要檢測的變量類型,sizeof 就會返回所占用的字節(jié)數(shù)了。

例如,我們要檢測 int 類型的大小,就可以這樣寫:

sizeof(int)

在編譯時,sizeof(int) 就會被替換為 int 類型所占用的字節(jié)數(shù)了。

在我的電腦上,sizeof(int) 是 4,也就是說 int 類型在我的電腦的內(nèi)存中占據(jù) 4 個字節(jié)。在你的電腦上,也許是 4,但也可能是其他的值。

我們用一個例子來測試一下吧:

// octet 是英語“字節(jié)”的意思,和 byte 類似
printf("char : %d octets\n", sizeof(char));
printf("int : %d octets\n", sizeof(int));
printf("long : %d octets\n", sizeof(long));
printf("double : %d octets\n", sizeof(double));

在我的電腦(64 位)運行,輸出:

char : 1 octets
int : 4 octets
long : 8 octets
double : 8 octets

我們并沒有測試所有已知的變量類型,你也可以課后自己去測試一下其他的類型,例如:short,float。

曾幾何時,當(dāng)電腦的內(nèi)存很小的年代,有這么多不同大小的變量類型可供選擇是一件很好的事,因為我們可以選“夠用的最小的”那種變量類型,以節(jié)約內(nèi)存。

現(xiàn)在,電腦的內(nèi)存一般都很大,“有錢任性”么。所以我們在編程時也沒必要太“拘謹(jǐn)”。不過在嵌入式領(lǐng)域,內(nèi)存大小一般是有限的,我們就得斟酌著使用變量類型了。

既然 sizeof 這么好用,我們可不可以用它來顯示我們自定義的變量類型的大小呢?例如 struct,enum,union。

是可以的。寫一個程序測試一下:

#include <stdio.h>

typedef struct Coordinate
{
    int x;
    int y;
} Coordinate;

int main(int argc, char *argv[])
{
    printf("Coordinate 結(jié)構(gòu)體的大小是 : %d 個字節(jié)\n", sizeof(Coordinate));

    return 0;
}

運行輸出:

Coordinate 結(jié)構(gòu)體的大小是 : 8 個字節(jié)

對于內(nèi)存的全新視角


之前,我們在繪制內(nèi)存圖示時,還是比較不精準(zhǔn)的。現(xiàn)在,我們知道了每個變量所占用的大小,我們的內(nèi)存圖示就可以變得更加精準(zhǔn)了。

假如我定義一個 int 類型的變量:

int age = 17;

我們用 sizeof 測試后得知 int 的大小為 4。假設(shè)我們的變量 age 被分配到的內(nèi)存地址起始是 1700,那么我們的內(nèi)存圖示就如下所示:

我們看到,我們的 int 型變量 age 在內(nèi)存中占用 4 個字節(jié),起始地址是 1700(它的內(nèi)存地址),一直到 1703。

如果我們對一個 char 型變量(大小是一個字節(jié))同樣賦值:

char number = 17;

那么,其內(nèi)存圖示是這樣的:

假如是一個 int 型的數(shù)組:

int age[100];

用 sizeof() 測試一下,就可以知道在內(nèi)存中 age 數(shù)組占用 400 個字節(jié)。4 * 100 = 400。

即使這個數(shù)組沒有賦初值,但是在內(nèi)存中仍然占據(jù) 400 個字節(jié)的空間。變量一聲明,在內(nèi)存中就為它分配一定大小的內(nèi)存了。

那么,如果我們創(chuàng)建一個類型是 Coordinate 的數(shù)組呢?

Coordinate coordinate[100];

其大小就是 8 * 100 = 800 個字節(jié)了。

3. 內(nèi)存的動態(tài)分配


好了,現(xiàn)在我們就進(jìn)入這一課的關(guān)鍵部分了,重提一次這一課的目的:學(xué)會如何手動申請內(nèi)存空間。

我們需要引入 stdlib.h 這個標(biāo)準(zhǔn)庫頭文件,因為接下來要使用的函數(shù)是定義在這個庫里面。

這兩個函數(shù)是什么呢?就是:

  • malloc:是 Memory Allocation 的縮寫,表示“內(nèi)存分配”。詢問操作系統(tǒng)能否預(yù)支一塊內(nèi)存空間來使用。

  • free:表示“解放,釋放,自由的”。意味著“釋放那塊內(nèi)存空間”。告訴操作系統(tǒng)我們不再需要這塊已經(jīng)分配的空間了,這塊內(nèi)存空間會被釋放,另一個程序就可以使用這塊空間了。

當(dāng)我們手動分配內(nèi)存時,須要按照以下三步順序來:

  1. 調(diào)用 malloc 函數(shù)來申請內(nèi)存空間。

  2. 檢測 malloc 函數(shù)的返回值,以得知操作系統(tǒng)是否成功為我們的程序分配了這塊內(nèi)存空間。

  3. 一旦使用完這塊內(nèi)存,不再需要時,必須用 free 函數(shù)來釋放占用的內(nèi)存,不然可能會造成內(nèi)存泄漏。

以上三個步驟是不是讓我們回憶起關(guān)于上一課“文件讀寫”的內(nèi)容了?

這三個步驟和文件指針的操作有點類似,也是先申請內(nèi)存,檢測是否成功,用完釋放。

malloc 函數(shù):申請內(nèi)存


malloc 分配的內(nèi)存是在堆上,一般的局部變量(自動分配的)大多是在棧上。

關(guān)于堆和棧的區(qū)別,還有內(nèi)存的其他區(qū)域,如靜態(tài)區(qū)等,大家可以自己延伸閱讀。

之前“字符串”那一課里已經(jīng)給出過一張圖表了。再來回顧一下吧:

名稱 內(nèi)容
代碼段 可執(zhí)行代碼、字符串常量
數(shù)據(jù)段 已初始化全局變量、已初始化全局靜態(tài)變量、局部靜態(tài)變量、常量數(shù)據(jù)
BSS段 未初始化全局變量,未初始化全局靜態(tài)變量
局部變量、函數(shù)參數(shù)
動態(tài)內(nèi)存分配

給出 malloc 函數(shù)的原型,你會發(fā)現(xiàn)有點滑稽:

void* malloc(size_t numOctetsToAllocate);

可以看到,malloc 函數(shù)有一個參數(shù) numOctetsToAllocate,就是需要申請的內(nèi)存空間大小(用字節(jié)數(shù)表示),這里的 size_t(之前的課程有提到過)其實和 int 是類似的,就是一個 define 宏定義,實際上很多時候就是 int。

對于我們目前的演示程序,可以將 sizeof(int) 置于 malloc 的括號中,表示要申請 int 類型的大小的空間。

真正引起我們興趣的是 malloc 函數(shù)的返回值:

void*

如果你還記得我們在函數(shù)那章所說的,void 表示“空”,我們用 void 來表示函數(shù)沒有返回值。

所以說,這里我們的函數(shù) malloc 會返回一個指向 void 的指針,一個指向“空”(void 表示“虛無,空”)的指針,有什么意義呢?malloc 函數(shù)的作者不會搞錯了吧?

不要擔(dān)心,這么做肯定是有理由的。

難道有人敢質(zhì)疑老爺子 Dennis Ritchie(C語言的作者)的智商?
來人吶,拖出去... 罰寫 100 個 C語言小游戲。

事實上,這個函數(shù)返回一個指針,指向操作系統(tǒng)分配的內(nèi)存的首地址。

如果操作系統(tǒng)在 1700 這個地址為你開辟了一塊內(nèi)存的話,那么函數(shù)就會返回一個包含 1700 這個值的指針。

但是,問題是:malloc 函數(shù)并不知道你要創(chuàng)建的變量是什么類型的。

實際上,你只給它傳遞了一個參數(shù): 在內(nèi)存中你需要申請的字節(jié)數(shù)。

如果你申請 4 個字節(jié),那么有可能是 int 類型,也有可能是 long 類型。

正因為 malloc 不知道自己應(yīng)該返回什么變量類型(它也無所謂,只要分配了一塊內(nèi)存就可以了),所以它會返回 void* 這個類型。這是一個可以表示任意指針類型的指針。

void* 與其他類型的指針之間可以通過強制轉(zhuǎn)換來相互轉(zhuǎn)換。例如:

int *i = (int *)p;  // p 是一個 void* 類型的指針

void *v = (void *)c;  // c 是一個 char* 類型的指針

實踐


如果我實際來用 malloc 函數(shù)分配一個 int 型指針:

int *memoryAllocated = NULL;  // 創(chuàng)建一個 int 型指針

memoryAllocated = malloc(sizeof(int));  // malloc 函數(shù)將分配的地址賦值給我們的指針 memoryAllocated

經(jīng)過上面的兩行代碼,我們的 int 型指針 memoryAllocated 就包含了操作系統(tǒng)分配的那塊內(nèi)存地址的首地址值。

假如我們用之前我們的圖示來舉例,這個值就是 1700。

檢測指針


既然上面我們用兩行代碼使得 memoryAllocated 這個指針包含了分配到的地址的首地址值,那么我們就可以通過檢測 memoryAllocated 的值來判斷申請內(nèi)存是否成功了:

  1. 如果為 NULL,則說明 malloc 調(diào)用沒有成功。

  2. 否則,就說明成功了。

一般來說內(nèi)存分配不會失敗,但是也有極端情況:

  1. 你的內(nèi)存(堆內(nèi)存)已經(jīng)不夠了。

  2. 你申請的內(nèi)存值大得離譜(比如你申請 64 GB 的內(nèi)存空間,那我想大多數(shù)電腦都是不可能分配成功的)。

希望大家每次用 malloc 函數(shù)時都要做指針的檢測,萬一真的出現(xiàn)返回值為 NULL 的情況,那我們需要立即停止程序,因為沒有足夠的內(nèi)存,也不可能進(jìn)行下面的操作了。

為了中斷程序的運行,我們來使用一個新的函數(shù):

exit()

exit 函數(shù)定義在 stdlib.h 中,調(diào)用此函數(shù)會使程序立即停止。

這個函數(shù)也只有一個參數(shù),就是返回值,這和 return 函數(shù)的參數(shù)是一樣原理的。實例:

int main(int argc, char *argv[])
{
    int *memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));

    if (memoryAllocated == NULL)  // 如果分配內(nèi)存失敗
    {
        exit(0);  // 立即停止程序
    }

    // 如果指針不為 NULL,那么可以繼續(xù)進(jìn)行接下來的操作

    return 0;
}

另外一個問題:用 malloc 函數(shù)申請 0 字節(jié)內(nèi)存會返回 NULL 指針嗎?


可以測試一下,也可以去查找關(guān)于 malloc 函數(shù)的說明文檔。

申請 0 字節(jié)內(nèi)存,函數(shù)并不返回 NULL,而是返回一個正常的內(nèi)存地址。
但是你卻無法使用這塊大小為 0 的內(nèi)存!

這就好比尺子上的某個刻度,刻度本身并沒有長度,只有某兩個刻度一起才能量出長度。

對于這一點一定要小心,因為這時候 if(NULL != p) 語句校驗將不起作用。

free函數(shù):釋放內(nèi)存


記得上一課我們使用 fclose 函數(shù)來關(guān)閉一個文件指針,也就是釋放占用的內(nèi)存。

free 函數(shù)的原理和 fclose 是類似的,我們用它來釋放一塊我們不再需要的內(nèi)存。原型:

void free(void* pointer);

free 函數(shù)只有一個目的:釋放 pointer 指針?biāo)赶虻哪菈K內(nèi)存。

實例程序:

int main(int argc, char *argv[])
{
    int* memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));

    if (memoryAllocated == NULL)  // 如果分配內(nèi)存失敗
    {
        exit(0);  // 立即停止程序
    }

    // 此處添加使用這塊內(nèi)存的代碼

    free(memoryAllocated);  // 我們不再需要這塊內(nèi)存了,釋放之

    return 0;
}

綜合上面的三個步驟,我們來寫一個完整的例子:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int* memoryAllocated = NULL;

    memoryAllocated = malloc(sizeof(int));  // 分配內(nèi)存

    if (memoryAllocated == NULL)  // 檢測是否分配成功
    {
        exit(0);  // 不成功,結(jié)束程序
    }

    // 使用這塊內(nèi)存
    printf("您幾歲了 ? ");

    scanf("%d", memoryAllocated);

    printf("您已經(jīng) %d 歲了\n", *memoryAllocated);

    free(memoryAllocated);  // 釋放這塊內(nèi)存

    return 0;
}

運行輸出:

您幾歲了 ? 32
您已經(jīng) 32 歲了

以上就是我們用動態(tài)分配的方式來創(chuàng)建了一個 int 型變量,使用它,釋放它所占用的內(nèi)存。

但是,我們也完全可以用以前的方式來實現(xiàn),如下:

int main(int argc, char *argv[])
{
    int myAge = 0; // 分配內(nèi)存 (自動)

    // 使用這塊內(nèi)存
    printf("您幾歲了 ? ");

    scanf("%d", &myAge);

    printf("你已經(jīng) %d 歲了\n", myAge);

    return 0;
}  // 釋放內(nèi)存 (在函數(shù)結(jié)束后自動釋放)

在這個簡單使用場景下,兩種方式(手動和自動)都是能完成任務(wù)的。

總結(jié)說來,創(chuàng)建一個變量(說到底也就是分配一塊內(nèi)存空間)有兩種方式:自動和手動。

  • 自動:我們熟知并且一直使用到現(xiàn)在的方式。

  • 手動(動態(tài)):這一課我們學(xué)習(xí)的內(nèi)容。

你可能會說:“我發(fā)現(xiàn)動態(tài)分配內(nèi)存的方式既復(fù)雜又沒什么用嘛!”

復(fù)雜么?還行吧,確實相對自動的方式要考慮比較多的因素。

沒有用么?絕不!

因為很多時候我們不得不使用手動的方式來分配內(nèi)存。

接下來我們就來看一下手動方式的必要性。

4. 動態(tài)分配一個數(shù)組


暫時我們只是用手動方式來創(chuàng)建了一個簡單的變量。

然而,一般說來,我們的動態(tài)分配可不是這樣“大材小用”的。

如果只是創(chuàng)建一個簡單的變量,我們用自動的方式就夠了。

那你會問:“啥時候須要用動態(tài)分配啊?”

問得好。動態(tài)分配最常被用來創(chuàng)建在運行時才知道大小的變量,例如動態(tài)數(shù)組。

假設(shè)我們要存儲一個用戶的朋友的年齡列表,按照我們以前的方式(自動方式),我們可以創(chuàng)建一個 int 型的數(shù)組:

int ageFriends[18];

很簡單對嗎?那問題不就解決了?

但是以上方式有兩個缺陷:

  1. 你怎么知道這個用戶只有 18 個朋友呢?可能他有更多朋友呢。

  2. 你說:“那好,我就創(chuàng)建一個數(shù)組:

int ageFriends[10000];

足夠儲存 1 萬個朋友的年齡。”

但是問題是:可能我們使用到的只是這個大數(shù)組的很小一部分,豈不是浪費內(nèi)存嘛。

最恰當(dāng)?shù)姆绞绞窃儐栍脩羲卸嗌倥笥眩缓髣?chuàng)建對應(yīng)大小的數(shù)組。

而這樣,我們的數(shù)組大小就只有在運行時才能知道了。

Voila,這就是動態(tài)分配的優(yōu)勢了:

  1. 可以在運行時才確定申請的內(nèi)存空間大小。

  2. 不多不少剛剛好,要多少就申請多少,不怕不夠或過多。

所以借著動態(tài)分配,我們就可以在運行時詢問用戶他到底有多少朋友。

如果他說有 20 個,那我們就申請 20 個 int 型的空間;如果他說有 50 個,那就申請 50 個。經(jīng)濟(jì)又環(huán)保。

我們之前說過,C語言中禁止用變量名來作為數(shù)組大小,例如不能這樣:

int ageFriends[numFriends];  // numFriends 是一個變量

盡管有的 C編譯器可能允許這樣的聲明,但是我們不推薦。

我們來看看用動態(tài)分配的方式如何實現(xiàn)這個程序:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int numFriends = 0, i = 0;

    int *ageFriends= NULL;  // 這個指針用來指示朋友年齡的數(shù)組

    // 詢問用戶有多少個朋友
    printf("請問您有多少朋友 ? ");

    scanf("%d", &numFriends);

    if (numFriends > 0)  // 至少得有一個朋友吧,不然也太慘了 :P
    {
        ageFriends = malloc(numFriends * sizeof(int));  // 為數(shù)組分配內(nèi)存
        if (ageFriends== NULL)  // 檢測分配是否成功
        {
            exit(0); // 分配不成功,退出程序
        }

        // 逐個詢問朋友年齡
        for (i = 0 ; i < numFriends; i++)  {
            printf("第%d位朋友的年齡是 ? ", i + 1);
            scanf("%d", &ageFriends[i]);
        }

        // 逐個輸出朋友的年齡
        printf("\n\n您的朋友的年齡如下 :\n");
        for (i = 0 ; i < numFriends; i++) {
            printf("%d 歲\n", ageFriends[i]);
        }

        // 釋放 malloc 分配的內(nèi)存空間,因為我們不再需要了
        free(ageFriends);
    }

    return 0;
}

運行輸出:

請問您有多少朋友 ? 7
第1位朋友的年齡是 ? 25
第2位朋友的年齡是 ? 21
第3位朋友的年齡是 ? 27
第4位朋友的年齡是 ? 18
第5位朋友的年齡是 ? 14
第6位朋友的年齡是 ? 32
第7位朋友的年齡是 ? 30

您的朋友的年齡如下 :
25歲
21歲
27歲
18歲
14歲
32歲
30歲

當(dāng)然了,這個程序比較簡單,但我向你保證以后的課程會使用動態(tài)分配來做更有趣的事。

5. 總結(jié)


  1. 不同類型的變量在內(nèi)存中所占的大小不盡相同。

  2. 借助 sizeof 這個關(guān)鍵字(也是運算符)可以知道一個類型所占的字節(jié)數(shù)。

  3. 動態(tài)分配就是在內(nèi)存中手動地預(yù)留一塊空間給一個變量或者數(shù)組。

  4. 動態(tài)分配的常用函數(shù)是 malloc(當(dāng)然,還有 calloc,realloc,可以查閱使用方法,和 malloc 是類似的),但是在不需要這塊內(nèi)存之后,千萬不要忘了使用 free 函數(shù)來釋放。而且,malloc 和 free 要一一對應(yīng),不能一個 malloc 對應(yīng)兩個 free,會出錯;或者兩個 malloc 對應(yīng)一個 free,會內(nèi)存泄露!

  5. 動態(tài)分配使得我們可以創(chuàng)建動態(tài)數(shù)組,就是它的大小在運行時才能確定。

6. 第二部分第九課預(yù)告


今天的課就到這里,一起加油吧!

下一課: C語言探索之旅 | 第二部分第九課: 實戰(zhàn)"懸掛小人"游戲


我是 謝恩銘,公眾號「程序員聯(lián)盟」(微信號:coderhub)運營者,慕課網(wǎng)精英講師 Oscar 老師,終生學(xué)習(xí)者。
熱愛生活,喜歡游泳,略懂烹飪。
人生格言:「向著標(biāo)桿直跑」

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

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