Solidity
在合約建構子中初始化狀態變數還是直接在聲明中初始化?
在合約建構子中初始化變數或直接在聲明中初始化變數有什麼區別嗎?最佳做法是什麼?
contract A { uint public storage = 10000; constructor(){} }
或者
contract B { uint public storage; constructor(){ storage = 10000; } }
初始化每個變數的位置決定了初始化程式碼何時執行。在初始化期間,程式碼按以下順序執行:
- 在狀態變數聲明中用作初始值設定項的表達式(最少派生到最多派生)。
- 基本建構子參數(從最多到最少派生)。
- 建構子體(最少到最多派生)。
如果每個變數的初始化都是獨立的,這沒有實際區別。您可能會簡單地獲得功能等效的字節碼,但某些指令的順序略有不同。
如果變數相互依賴,您實際上可能會得到不同的結果。在所有這些地方,您都可以引用其他狀態變數並呼叫任意函式(甚至是外部函式)。您呼叫的函式可能會產生副作用或修改狀態變數(甚至在它們被初始化之前)。
為避免由此產生的問題,最好保持簡單。避免依賴關係,對於聲明和基本建構子參數,盡量只使用不讀取或寫入其他狀態變數的簡單常量或函式。如果您需要做一些更複雜的事情,請將該邏輯保留在建構子主體中,以便更容易推斷出以什麼順序執行的內容。
實際例子
您的範例中的字節碼差異
您的範例是一個非常簡單的場景,契約行為顯然沒有區別,但讓我們看看幕後發生了什麼。
a.sol
contract A { uint public s = 10000; }
b.sol
contract B { uint public s; constructor() { s = 10000; } }
這是兩種情況下生成的程序集之間的區別:
solc a.sol --optimize --asm --debug-info none > a.asm solc b.sol --optimize --asm --debug-info none > b.asm diff --unified a.asm b.asm
@@ -1,10 +1,7 @@ -======= a.sol:A ======= +======= b.sol:B ======= EVM assembly: mstore(0x40, 0x80) - 0x2710 - 0x00 - sstore callvalue dup1 iszero @@ -15,6 +12,9 @@ revert tag_1: pop + 0x2710 + 0x00 + sstore dataSize(sub_0) dup1 dataOffset(sub_0) @@ -66,6 +66,6 @@ swap1 return - auxdata: 0xa26469706673582212201cfb33d8ca060adae5b385c6039c60778e487b6364ff4076e825b1d418fd206964736f6c634300080a0033 + auxdata: 0xa264697066735822122027a7491b2543ebe695a4bc9b47f2110e1e14a5212f2c0ae7b5a34cda7f996e3d64736f6c634300080a0033 }
如您所見,唯一真正的區別是,在沒有建構子的情況下,初始化發生得更早,但最終結果仍然相同。
如果您嘗試使用新的(仍然是實驗性的)程式碼生成器(
solc --optimize --ir-optimized
),您實際上最終會得到完全相同的彙編程式碼。初始化順序
讓我們看一個說明初始化順序的範例:
contract A { string[] order; constructor(uint _x) { log("constructor of A"); } function log(string memory _word) internal returns (uint) { order.push(_word); return 42; } } contract B is A { uint b = log("declaration of b"); constructor(uint _x) A(log("args of A")) { log("constructor of B"); } } contract C is B { uint c = log("declaration of c"); constructor() B(log("args of B")) { log("constructor of C"); } function get() public view returns (string[] memory) { return order; } }
如果您部署上面的程式碼並執行
C.get()
,您將看到以下值:
"declaration of b"
"declaration of c"
"args of B"
"args of A"
"constructor of A"
"constructor of B"
"constructor of C"
變數之間的依賴關係
這些規則的結果是可以在變數初始化之前訪問它:
contract C { uint a = 1; uint b; uint c = b + 1; constructor() { b = 2; } function get() public view returns (uint, uint, uint) { return (a, b, c); } }
在這個例子中,執行
C.get()
會給你(1, 2, 1)
而不是(1, 2, 3)
你可能期望的。這是因為C.c
在C.b
. 未使用的儲存總是零初始化,因此C.c
被分配0 + 1
。