Solidity

在合約建構子中初始化狀態變數還是直接在聲明中初始化?

  • November 28, 2021

在合約建構子中初始化變數或直接在聲明中初始化變數有什麼區別嗎?最佳做法是什麼?

contract A {
  uint public storage = 10000;
  constructor(){}
}

或者

contract B {
  uint public storage;
  constructor(){
     storage = 10000;
  }
}

初始化每個變數的位置決定了初始化程式碼何時執行。在初始化期間,程式碼按以下順序執行:

  1. 在狀態變數聲明中用作初始值設定項的表達式(最少派生到最多派生)。
  2. 基本建構子參數(從最多到最少派生)。
  3. 建構子體(最少到最多派生)。

如果每個變數的初始化都是獨立的,這沒有實際區別。您可能會簡單地獲得功能等效的字節碼,但某些指令的順序略有不同。

如果變數相互依賴,您實際上可能會得到不同的結果。在所有這些地方,您都可以引用其他狀態變數並呼叫任意函式(甚至是外部函式)。您呼叫的函式可能會產生副作用或修改狀態變數(甚至在它們被初始化之前)。

為避免由此產生的問題,最好保持簡單。避免依賴關係,對於聲明和基本建構子參數,盡量只使用不讀取或寫入其他狀態變數的簡單常量或函式。如果您需要做一些更複雜的事情,請將該邏輯保留在建構子主體中,以便更容易推斷出以什麼順序執行的內容。

實際例子

您的範例中的字節碼差異

您的範例是一個非常簡單的場景,契約行為顯然沒有區別,但讓我們看看幕後發生了什麼。

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.cC.b. 未使用的儲存總是零初始化,因此C.c被分配0 + 1

引用自:https://ethereum.stackexchange.com/questions/114287