智慧合約從入門到精通:Solidity語法之記憶體變數的佈局和狀態變數的儲存模型

區塊鏈技術發表於2018-07-05

簡介:在前面我們已經講過Solidity語言的一些語法內容,在矩陣元JUICE開放平臺的JIDE開發時,一定要注意Layout in Memory和Layout of State Variables in Storage,即記憶體變數的佈局和狀態變數的儲存模型。記憶體變數的佈局(Layout in Memory)

Solidity預留了3個32位元組大小的槽位:

  • 0-64:雜湊方法的暫存空間(scratch space)

  • 64-96:當前已分配記憶體大小(也稱空閒記憶體指標(free memory pointer))

暫存空間可在語句之間使用(如在內聯編譯時使用)

Solidity總是在空閒記憶體指標所在位置建立一個新物件,且對應的記憶體永遠不會被釋放(也許未來會改變這種做法)。

有一些在Solidity中的操作需要超過64位元組的臨時空間,這樣就會超過預留的暫存空間。他們就將會分配到空閒記憶體指標所在的地方,但由於他們自身的特點,生命週期相對較短,且指標本身不能更新,記憶體也許會,也許不會被清零(zerod out)。因此,大家不應該認為空閒的記憶體一定已經是清零(zeroed out)的。

狀態變數的儲存模型(Layout of State Variables in Storage)

大小固定的變數(除了對映,變長陣列以外的所有型別)在儲存(storage)中是依次連續從位置0開始排列的。如果多個變數佔用的大小少於32位元組,會盡可能的打包到單個storage槽位裡,具體規則如下:

  • 在storage槽中第一項是按低位對齊儲存(lower-order aligned)(譯者注:意味著是大端序了,因為是按書寫順序。)。

  • 基本型別儲存時僅佔用其實際需要的位元組。

  • 如果基本型別不能放入某個槽位餘下的空間,它將被放入下一個槽位。

  • 結構體和陣列總是使用一個全新的槽位,並佔用整個槽(但在結構體內或陣列內的每個項仍遵從上述規則)

優化建議

當使用的元素佔用少於32位元組,你的合約的gas使用也許更高。這是因為EVM每次操作32位元組。因此,如果元素比這小,EVM需要更多操作來從32位元組減少到需要的大小。 因為編譯器會將多個元素打包到一個storage槽位,這樣就可以將多次讀或寫組合進一次操作中,只有在這時,通過縮減變數大小來優化儲存結構才有意義。當操作函式引數和memory的變數時,因為編譯器不會這樣優化,所以沒有上述的意義。

最後,為了方便EVM進行優化,嘗試有意識排序storage的變數和結構體的成員,從而讓他們能打包得更緊密。比如,按這樣的順序定義,uint128, uint128, uint256,而不是uint128, uint256, uint128。因為後一種會佔用三個槽位。

非固定大小

結構體和陣列裡的元素按它們給定的順序儲存。

由於它們不可預知的大小。對映和變長陣列型別,使用Keccak-256雜湊運算來找真正資料儲存的起始位置。這些起始位置往往是完整的堆疊槽。

對映和動態陣列根據上述規則在位置p佔用一個未滿的槽位(對對映裡巢狀對映,陣列中巢狀陣列的情況則遞迴應用上述規則)。對一個動態陣列,位置p這個槽位儲存陣列的元素個數(位元組陣列和字串例外,見下文)。而對於對映,這個槽位沒有填充任何資料(但這是必要的,因為兩個挨著的對映將會得到不同的雜湊值)。陣列的原始資料位置是keccak256(p);而對映型別的某個鍵k,它的資料儲存位置則是keccak256(k . p),其中的.表示連線符號。如果定位到的值以是一個非基本型別,則繼續運用上述規則,是基於keccak256(k . p)的新的偏移offset。

bytes和string佔用的位元組大小如果足夠小,會把其自身長度和原始資料存在當前的槽位。具體來說,如果資料最多31位長,高位存資料(左對齊),低位儲存長度lenght * 2。如果再長一點,主槽位就只存lenght * 2 + 1。原始資料按普通規則儲存在keccak256(slot)

所以對於接下來的程式碼片段:

pragma solidity ^0.4.4;

contract C {
    struct s { uint a; uint b; }
    uint x;
    mapping(uint => mapping(uint => s)) data;
}
複製程式碼

按上面的程式碼來看,結構體從位置0開始,這裡定義了一個結構體,但並沒有對應的結構體變數,故不佔用空間。uint x實際是uint256,單個佔32位元組,佔用槽位0,所以對映data將從槽位1開始。 data[4][9].b的位置在keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1 有人在這裡嘗試直接讀取區塊鏈的儲存值,https://github.com/ethereum/solidity/issues/1550

參考內容:https://open.juzix.net/doc

智慧合約開發教程視訊:區塊鏈系列視訊課程之智慧合約簡介

相關文章