# 狀態變量的存儲模型(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://github.com/ethereum/solidity/issues/1550)
- Solidity語言
- 入門說明
- Solidity智能合約文件結構
- 智能合約源文件的基本要素概覽
- 值類型
- 類型
- 布爾
- 整型
- 地址
- 字節數組
- 小數
- 字符串
- 十六進制字面量
- 枚舉
- 函數
- 引用類型
- 引用類型
- 數據位置
- 數組
- 數據結構
- 雜項
- 映射
- 左值運算符
- 類型間的轉換
- 類型推斷
- 單位
- 貨幣單位
- 時間單位
- 語言內置特性
- 特殊變量及函數
- 數學和加密函數
- 地址相關
- 進階
- 入參和出參
- 控制結構
- 函數調用
- 創建合約實例
- 表達式的執行順序
- 賦值
- 作用范圍和聲明
- 異常
- 內聯匯編
- 合約詳解
- 合約
- 可見性或權限控制
- 訪問函數
- 函數修改器
- 常狀態變量
- 回退函數
- 事件
- 繼承
- 接口
- 其它
- 庫
- 狀態變量的存儲模型
- 內存變量的存局
- 調用數據的布局