深入了解以太坊虛擬機第3部分——動態(tài)數(shù)據(jù)類型的表示方法

本文由幣乎社區(qū)(bihu.com)內(nèi)容支持計劃贊助屉符。

Solidity提供了在其他編程語言常見的數(shù)據(jù)類型匠楚。除了簡單的值類型比如數(shù)字和結(jié)構(gòu)體雏亚,還有一些其他數(shù)據(jù)類型偿乖,隨著數(shù)據(jù)的增加可以進行動態(tài)擴展的動態(tài)類型击罪。動態(tài)類型的3大類:

  • 映射(Mappings):mapping(bytes32 => uint256)mapping(address => string)等等
  • 數(shù)組(Arrays):[]uint256贪薪,[]byte等等
  • 字節(jié)數(shù)組(Byte arrays):只有兩種類型:string媳禁,bytes

在本系列的第二篇文章中我們看見了固定大小的簡單類型在內(nèi)存中的表示方式。

  • 基本數(shù)值:uint256画切,byte等等
  • 定長數(shù)組:[10]uint8竣稽,[32]bytebytes32
  • 組合了上面類型的結(jié)構(gòu)體

固定大小的存儲變量都是盡可能的打包成32字節(jié)的塊然后依次存放在存儲器中的。(如果這看起來很陌生毫别,請閱讀本系列的第二篇文章: 固定長度數(shù)據(jù)類型的表示方法

在本文中我們將會研究Solidity是如何支持更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu)的娃弓。在表面上看可能Solidity中的數(shù)組和映射比較熟悉,但是從它們的實現(xiàn)方式來看在本質(zhì)上卻有著不同的性能特征拧烦。

我們會從映射開始忘闻,這是三者當(dāng)中最簡單的。數(shù)組和字節(jié)數(shù)組其實就是擁有更加高級特征的映射恋博。

映射

讓我們存儲一個數(shù)值在uint256 => uint256映射中:

pragma solidity ^0.4.11;
contract C {
    mapping(uint256 => uint256) items;
    function C() {
      items[0xC0FEFE] = 0x42;
    }
}

編譯:

solc --bin --asm --optimize c-mapping.sol

匯編代碼:

tag_2:
  // 不做任何事情齐佳,應(yīng)該會被優(yōu)化掉
  0xc0fefe
  0x0
  swap1
  dup2
  mstore
  0x20
  mstore
  // 將0x42 存儲在地址0x798...187c上
  0x42
 0x79826054ee948a209ff4a6c9064d7398508d2c1909a392f899d301c6d232187c
  sstore

我們可以將EVM想成一個鍵-值( key-value)數(shù)據(jù)庫,不過每個key都限制為32字節(jié)债沮。與其直接使用key0xC0FEFE炼吴,不如使用key的哈希值0x798...187c,并且0x42存儲在這里疫衩。哈希函數(shù)使用的是keccak256(SHA256)函數(shù)硅蹦。

在這個例子中我們沒有看見keccak256指令本身,因為優(yōu)化器已經(jīng)提前計算了結(jié)果并內(nèi)聯(lián)到了字節(jié)碼中闷煤。在沒什么作用的mstore指令中童芹,我們依然可以看到計算的痕跡。

計算地址

使用一些Python代碼來把0xC0FEFE哈希成0x798...187c鲤拿。如果你想要跟著做下去假褪,你需要安裝Python 3.6,或者安裝pysha3 來獲得keccak_256哈希函數(shù)近顷。

定義兩個協(xié)助函數(shù):

import binascii
import sha3
#將數(shù)值轉(zhuǎn)換成32字節(jié)數(shù)組
def bytes32(i):
    return binascii.unhexlify('%064x' % i)
# 計算32字節(jié)數(shù)組的 keccak256 哈希值
def keccak256(x):
    return sha3.keccak_256(x).hexdigest()

將數(shù)值轉(zhuǎn)換成32個字節(jié):

>>> bytes32(1)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
>>> bytes32(0xC0FEFE)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xfe\xfe'

使用+操作符生音,將兩個字節(jié)數(shù)組連接起來:

>>> bytes32(1) + bytes32(2)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02'

計算一些字節(jié)的 keccak256 哈希值:

>>> keccak256(bytes(1))
'bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a'

現(xiàn)在我們可以計算0x798...187c了。

存儲變量items的位置是0x0(因為它是第一個存儲變量)窒升。連接key0xc0fefeitems的位置來獲取地址:

# key = 0xC0FEFE, position = 0
>>> keccak256(bytes32(0xC0FEFE) + bytes32(0))
'79826054ee948a209ff4a6c9064d7398508d2c1909a392f899d301c6d232187c'

為key計算存儲地址的公式是:

keccak256(bytes32(key) + bytes32(position))

兩個映射

我們先把公式放在這里缀遍,后面數(shù)值存儲時需要計算會用到該公式。

假設(shè)我們的合約有兩個映射:

pragma solidity ^0.4.11;
contract C {
    mapping(uint256 => uint256) itemsA;
    mapping(uint256 => uint256) itemsB;
    function C() {
      itemsA[0xAAAA] = 0xAAAA;
      itemsB[0xBBBB] = 0xBBBB;
    }
}
  • itemsA的位置是0饱须,key為0xAAAA
# key = 0xAAAA, position = 0
>>> keccak256(bytes32(0xAAAA) + bytes32(0))
'839613f731613c3a2f728362760f939c8004b5d9066154aab51d6dadf74733f3'
  • itemsB的位置為1域醇,key為0xBBBB
# key = 0xBBBB, position = 1
>>> keccak256(bytes32(0xBBBB) + bytes32(1))
'34cb23340a4263c995af18b23d9f53b67ff379ccaa3a91b75007b010c489d395'

用編譯器來驗證一下這些計算:

$ solc --bin --asm --optimize  c-mapping-2.sol

匯編代碼:

tag_2:
  // ... 忽略可能會被優(yōu)化掉的內(nèi)存操作
  0xaaaa
  0x839613f731613c3a2f728362760f939c8004b5d9066154aab51d6dadf74733f3
  sstore
  0xbbbb
  0x34cb23340a4263c995af18b23d9f53b67ff379ccaa3a91b75007b010c489d395
  sstore

跟期望的結(jié)果一樣。

匯編代碼中的KECCAK256

編譯器可以提前計算key的地址是因為相關(guān)的值是常量蓉媳。如果key使用的是變量譬挚,那么哈希就必須要在匯編代碼中完成。現(xiàn)在我們無效化優(yōu)化器督怜,來看看在匯編代碼中哈希是如何完成的。

事實證明很容易就能讓優(yōu)化器無效狠角,只要引入一個間接的虛變量i

pragma solidity ^0.4.11;
contract C {
    mapping(uint256 => uint256) items;
    //這個變量會造成常量的優(yōu)化失敗
    uint256 i = 0xC0FEFE;
    function C() {
      items[i] = 0x42;
    }
}

變量items的位置依然是0x0号杠,所以我們應(yīng)該期待地址與之前是一樣的。

加上優(yōu)化選項進行編譯,但是這次不會提前計算哈希值:

$ solc --bin --asm --optimize  c-mapping--no-constant-folding.sol

注釋的匯編代碼:

tag_2:
  // 加載`i` 到棧中
  sload(0x1)
    [0xC0FEFE]
  // 將key`0xC0FEFE`存放在內(nèi)存中的0x0位置上姨蟋,為哈希做準(zhǔn)備
  0x0
    [0x0 0xC0FEFE]
  swap1
    [0xC0FEFE 0x0]
  dup2
    [0x0 0xC0FEFE 0x0]
  mstore
    [0x0]
    memory: {
      0x00 => 0xC0FEFE
    }
  // 將位置 `0x0` 存儲在內(nèi)存中的 0x20 (32)位置上屉凯,為哈希做準(zhǔn)備
  0x20 // 32
    [0x20 0x0]
  dup2
    [0x0 0x20 0x0]
  swap1
    [0x20 0x0 0x0]
  mstore
    [0x0]
    memory: {
      0x00 => 0xC0FEFE
      0x20 => 0x0
    }
 // 從第0個字節(jié)開始,哈希在內(nèi)存中接下來的0x40(64)個字節(jié)
  0x40 // 64
    [0x40 0x0]
  swap1
    [0x0 0x40]
  keccak256
    [0x798...187c]
  // 將0x42 存儲在計算的地址上
  0x42
    [0x42 0x798...187c]
  swap1
    [0x798...187c 0x42]
  sstore
    store: {
      0x798...187c => 0x42
    }

mstore指令寫入32個字節(jié)到內(nèi)存中眼溶。內(nèi)存操作便宜很多悠砚,只需要3 gas就可以讀取和寫入。前半部分的匯編代碼就是通過將key和位置加載到相鄰的內(nèi)存塊中來進行“連接”的:

 0                   31  32                 63
[    key (32 bytes)    ][ position (32 bytes) ]

然后keccak256指令哈希內(nèi)存中的數(shù)據(jù)堂飞。成本取決于被哈希的數(shù)據(jù)有多少:

  • 每個SHA3操作需要支付 30 gas
  • 每個32字節(jié)的字需要支付 6 gas

對于一個uint256類型key灌旧,gas的成本是42:30 + 6 * 2

映射大數(shù)值

每個存儲槽只能存儲32字節(jié)绰筛。如果我們嘗試存儲一個更大一點的結(jié)構(gòu)體會怎么樣枢泰?

pragma solidity ^0.4.11;
contract C {
    mapping(uint256 => Tuple) tuples;
    struct Tuple {
      uint256 a;
      uint256 b;
      uint256 c;
    }
    function C() {
      tuples[0x1].a = 0x1A;
      tuples[0x1].b = 0x1B;
      tuples[0x1].c = 0x1C;
    }
}

編譯,你會看見3個sstore指令:

tag_2:
  //忽略未優(yōu)化的代碼
  0x1a
  0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7d
  sstore
  0x1b
  0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7e
  sstore
  0x1c
  0xada5013122d395ba3c54772283fb069b10426056ef8ca54750cb9bb552a59e7f
  sstore

注意計算的地址除了最后一個數(shù)字其他都是一樣的铝噩。Tulp結(jié)構(gòu)體成員是依次排列的(..7d, ..7e, ..7f)衡蚂。

映射不會打包

考慮到映射的設(shè)計方式,每項需要的最小存儲空間是32字節(jié)骏庸,即使你實際只需要存儲1個字節(jié):

pragma solidity ^0.4.11;
contract C {
    mapping(uint256 => uint8) items;
    function C() {
      items[0xA] = 0xAA;
      items[0xB] = 0xBB;
    }
}

如果一個數(shù)值大于32字節(jié)毛甲,那么你需要的存儲空間會以32字節(jié)依次增加。

動態(tài)數(shù)組是映射的升級

在典型語言中具被,數(shù)組只是連續(xù)存儲在內(nèi)存中一系列相同類型的元素玻募。假設(shè)你有一個包含100個uint8類型的元素數(shù)組,那么這就會占用100個字節(jié)的內(nèi)存硬猫。這種模式的話补箍,將整個數(shù)組加載到CPU的緩存中然后循環(huán)遍歷每個元素會便宜一點。

對于大多數(shù)語言而言啸蜜,數(shù)組比映射都會便宜一些坑雅。不過在Solidity中,數(shù)組是更加昂貴的映射衬横。數(shù)組里面的元素會按照順序排列在存儲器中:

0x290d...e563
0x290d...e564
0x290d...e565
0x290d...e566

但是請記住裹粤,對于這些存儲槽的每次訪問實際上就像數(shù)據(jù)庫中的key-value的查找一樣。訪問一個數(shù)組的元素跟訪問一個映射的元素是沒什么區(qū)別的蜂林。

思考一下[]uint256類型遥诉,它本質(zhì)上與mapping(uint256 => uint256)是一樣的,只不過后者多了一點特征噪叙,讓它看起去就像數(shù)組一樣矮锈。

  • length表示一共有多少個元素
  • 邊界檢查。當(dāng)讀取或?qū)懭霑r索引值大于length就會報錯
  • 比映射更加復(fù)雜的存儲打包行為
  • 當(dāng)數(shù)組變小時睁蕾,自動清除未使用的存儲槽
  • bytesstring的特殊優(yōu)化讓短數(shù)組(小于32字節(jié))存儲更加高效

簡單數(shù)組

看一下保存3個元素的數(shù)組:

// c-darray.sol
pragma solidity ^0.4.11;
contract C {
    uint256[] chunks;
    function C() {
      chunks.push(0xAA);
      chunks.push(0xBB);
      chunks.push(0xCC);
    }
}

數(shù)組訪問的匯編代碼難以追蹤苞笨,使用Remix調(diào)試器來運行合約:

模擬的最后债朵,我們可以看到有4個存儲槽被使用了:

key: 0x0000000000000000000000000000000000000000000000000000000000000000
value: 0x0000000000000000000000000000000000000000000000000000000000000003
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
value: 0x00000000000000000000000000000000000000000000000000000000000000aa
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564
value: 0x00000000000000000000000000000000000000000000000000000000000000bb
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e565
value: 0x00000000000000000000000000000000000000000000000000000000000000cc

chunks變量的位置是0x0,用來存儲數(shù)組的長度(0x3)瀑凝,哈希變量的位置來找到存儲數(shù)組數(shù)據(jù)的地址:

# position = 0
>>> keccak256(bytes32(0))
'290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563'

在這個地址上數(shù)組的每個元素依次排列(0x29..63序芦,0x29..640x29..65)粤咪。

動態(tài)數(shù)據(jù)打包

所有重要的打包行為是什么樣的谚中?數(shù)組與映射比較,數(shù)組的一個優(yōu)勢就是打包寥枝。擁有4個元素的uint128[]數(shù)組元素剛剛好需要2個存儲槽(再加1個存儲槽用來存儲長度)宪塔。

思考一下:

pragma solidity ^0.4.11;
contract C {
    uint128[] s;
    function C() {
        s.length = 4;
        s[0] = 0xAA;
        s[1] = 0xBB;
        s[2] = 0xCC;
        s[3] = 0xDD;
    }
}

在Remix中運行這個代碼,存儲器的最后看起來像這樣:

key: 0x0000000000000000000000000000000000000000000000000000000000000000
value: 0x0000000000000000000000000000000000000000000000000000000000000004
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
value: 0x000000000000000000000000000000bb000000000000000000000000000000aa
key: 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564
value: 0x000000000000000000000000000000dd000000000000000000000000000000cc

只有三個存儲槽被使用了脉顿,跟預(yù)料的一樣蝌麸。長度再次存儲在存儲變量的0x0位置上。4個元素被打包放入兩個獨立的存儲槽中艾疟。該數(shù)組的開始地址是變量位置的哈希值:

# position = 0
>>> keccak256(bytes32(0))
'290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563'

現(xiàn)在的地址是每兩個數(shù)組元素增加一次来吩,看起來很好!

但是匯編代碼本身優(yōu)化的并不好蔽莱。因為使用了兩個存儲槽弟疆,所以我們會希望優(yōu)化器使用兩個sstore指令來完成任務(wù)。不幸的是盗冷,由于邊界檢查(和一些其他因素)怠苔,所以沒有辦法將sstore指令優(yōu)化掉。

使用4個sstore指令才能完成任務(wù):

/* "c-bytes--sstore-optimize-fail.sol":105:116  s[0] = 0xAA */
sstore
/* "c-bytes--sstore-optimize-fail.sol":126:137  s[1] = 0xBB */
sstore
/* "c-bytes--sstore-optimize-fail.sol":147:158  s[2] = 0xCC */
sstore
/* "c-bytes--sstore-optimize-fail.sol":168:179  s[3] = 0xDD */
sstore

字節(jié)數(shù)組和字符串

bytesstring是為字節(jié)和字符進行優(yōu)化的特殊數(shù)組類型仪糖。如果數(shù)組的長度小于31字節(jié)柑司,只需要1個存儲槽來存儲整個數(shù)組。長一點的字節(jié)數(shù)組跟正常數(shù)組的表示方式差不多锅劝。

看看短一點的字節(jié)數(shù)組:

// c-bytes--long.sol
pragma solidity ^0.4.11;
contract C {
    bytes s;
    function C() {
        s.push(0xAA);
        s.push(0xBB);
        s.push(0xCC);
    }
}

因為數(shù)組只有3個字節(jié)(小于31字節(jié))攒驰,所以它只占用1個存儲槽。在Remix中運行故爵,存儲看起來如下:

key: 0x0000000000000000000000000000000000000000000000000000000000000000
value: 0xaabbcc0000000000000000000000000000000000000000000000000000000006

數(shù)據(jù)0xaabbcc...從左到右的進行存儲玻粪。后面的0是空數(shù)據(jù)。最后的0x06字節(jié)是數(shù)組的編碼長度诬垂。公式是長度=編碼長度/2劲室,在這個例子中實際長度是6/2=3

stringbytes的原理一模一樣结窘。

長字節(jié)數(shù)組

如果數(shù)據(jù)的長度大于31字節(jié)很洋,字節(jié)數(shù)組就跟[]byte一樣。來看一下長度為128字節(jié)的字節(jié)數(shù)組:

// c-bytes--long.sol
pragma solidity ^0.4.11;
contract C {
    bytes s;
    function C() {
        s.length = 32 * 4;
        s[31] = 0x1;
        s[63] = 0x2;
        s[95] = 0x3;
        s[127] = 0x4;
    }
}

在Remix中運行隧枫,可以看見使用了4個存儲槽:

0x0000...0000
0x0000...0101
0x290d...e563
0x0000...0001
0x290d...e564
0x0000...0002
0x290d...e565
0x0000...0003
0x290d...e566
0x0000...0004

0x0的存儲槽不再用來存儲數(shù)據(jù)喉磁,整個存儲槽現(xiàn)在存儲編碼的數(shù)組長度棺克。要獲得實際長度,使用長度=(編碼長度-1)/2公式线定。在這個例子中長度是(0x101 - 1)/2=128。實際的字節(jié)被保存在0x290d...e563确买,并且存儲槽是連續(xù)的斤讥。

字節(jié)數(shù)組的匯編代碼相當(dāng)多。除了正常的邊界檢查和數(shù)組恢復(fù)大小等湾趾,它還需要對長度進行編碼/解碼芭商,以及注意長字節(jié)數(shù)組和短字節(jié)數(shù)組之間的轉(zhuǎn)換。

為什么要編碼長度搀缠?因為編碼之后铛楣,可以很容易的測試出來字節(jié)數(shù)組是長還是短。注意對于長數(shù)組而言編碼長度總是奇數(shù)艺普,而短數(shù)組的編碼長度總是偶數(shù)簸州。匯編代碼只需要查看一下最后一位是否為0,為0就是偶數(shù)(短數(shù)組)歧譬,非0就是奇數(shù)(長數(shù)組)岸浑。

總結(jié)

查看Solidity編譯器的內(nèi)部工作,可以看見熟悉的數(shù)據(jù)結(jié)構(gòu)例如映射和數(shù)組與傳統(tǒng)編程語言完全不同瑰步。

概括:

  • 數(shù)組跟映射一樣矢洲,非高效
  • 比映射的匯編代碼更加復(fù)雜
  • 小類型(byteuint8缩焦,string)時存儲比映射高效
  • 匯編代碼優(yōu)化的不是很好读虏。即使是打包,每個任務(wù)都會有一個sstore指令

EVM的存儲器就是一個鍵值數(shù)據(jù)庫袁滥,跟git很像盖桥。如果你改變了任一東西,根節(jié)點的校驗和也會改變呻拌。如果兩個根節(jié)點擁有相同的校驗和葱轩,存儲的數(shù)據(jù)就能保證是一樣的。

為了體會Solidity和EVM的奇特藐握,可以想象一下在git倉庫里數(shù)組里面的每個元素都是它自己的文件靴拱。當(dāng)你改變數(shù)組里一個元素的值,實際上就相當(dāng)于創(chuàng)建了一個提交猾普。當(dāng)你迭代一個數(shù)組時袜炕,你不能一次性的加載整個數(shù)組,你必須要到倉庫中進行查找并分別找到每個文件初家。

不僅僅這樣偎窘,每個文件都限制到32字節(jié)乌助!因為我們需要將數(shù)據(jù)結(jié)構(gòu)都分割成32字節(jié)的塊,Solidity編譯器的所有邏輯和優(yōu)化都是很負責(zé)的陌知,全部在匯編的時候完成他托。

不過32字節(jié)的限制是完全任意的。支持鍵值存儲的可以使用key來存儲任意類型的數(shù)值仆葡。也許未來我們添加新的EVM指令使用key來存儲任意的字節(jié)數(shù)組赏参。

不過現(xiàn)在,EVM存儲器就是一個偽裝成32字節(jié)數(shù)組的鍵值數(shù)據(jù)庫沿盅。

可以看看ArrayUtils::resizeDynamicArray 來了解一下當(dāng)恢復(fù)數(shù)組大小時編譯器的動作把篓。正常情況下數(shù)據(jù)結(jié)構(gòu)都會作為語言的標(biāo)準(zhǔn)庫來完成的,但是在Solidity中嵌入到了編譯器里面腰涧。

本系列文章其他部分譯文鏈接:

翻譯作者: 許莉
原文地址:Diving Into The Ethereum VM Part Three

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末韧掩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子窖铡,更是在濱河造成了極大的恐慌疗锐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件费彼,死亡現(xiàn)場離奇詭異窒悔,居然都是意外死亡,警方通過查閱死者的電腦和手機敌买,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門简珠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虹钮,你說我怎么就攤上這事聋庵。” “怎么了芙粱?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵祭玉,是天一觀的道長。 經(jīng)常有香客問我春畔,道長脱货,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任律姨,我火速辦了婚禮振峻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘择份。我一直安慰自己扣孟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布荣赶。 她就那樣靜靜地躺著凤价,像睡著了一般鸽斟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上利诺,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天富蓄,我揣著相機與錄音,去河邊找鬼慢逾。 笑死格粪,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的氛改。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼比伏,長吁一口氣:“原來是場噩夢啊……” “哼胜卤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赁项,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤葛躏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悠菜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舰攒,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年悔醋,在試婚紗的時候發(fā)現(xiàn)自己被綠了摩窃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡芬骄,死狀恐怖猾愿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情账阻,我是刑警寧澤蒂秘,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站淘太,受9級特大地震影響姻僧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蒲牧,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一撇贺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冰抢,春花似錦显熏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缓升。三九已至,卻和暖如春蕴轨,著一層夾襖步出監(jiān)牢的瞬間港谊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工橙弱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歧寺,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓棘脐,卻偏偏與公主長得像斜筐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛀缝,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

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