Solidity語言學習筆記————41、記憶體佈局

FLy_鵬程萬里發表於2018-07-08

記憶體佈局(Layout in Memory)

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

  • 0x00 - 0x3f: 雜湊方法的暫存空間(scratch space)
  • 0x40 - 0x5f: 前已分配記憶體大小,也稱空閒記憶體指標(free memory pointer)
  • 0x60 - 0x7f: 零槽(zero slot)

暫存空間可在語句之間使用(如在內聯編譯時使用)。零槽用作動態記憶體陣列的初始值,不應寫入資料(空閒記憶體初始指標指向0x80)。

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

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

呼叫資料的佈局(Layout of CallData)

當部署一個合約,並且當它從一個帳戶被呼叫時,輸入資料被假定為ABI規範中的格式。ABI規範要求將引數填充到32位元組的倍數。內部函式呼叫使用不同的約定。

內部機制 - 清理變數(Internals - Cleaning Up Variables)

當一個值佔用的位數小於32位元組時,那些沒有用到的位必須被清除掉。Solidity編譯器設計實現為,在任何可能受到潛在的殘存資料帶來的副作用之前,清理掉這些髒資料。比如,在向記憶體寫入一個值前,不需要的位元組位需要被清除掉,因為沒有用到的記憶體位可能被用來計算雜湊,或作為訊息呼叫的傳送的資料儲存。同樣的,在向storage中儲存時,未用到的位元組位需要被清理掉,否則這些髒資料會帶來意想不到的事情。

另一方面,如果接下來的後述操作不會產生副作用,我們不會主動清理這些位元組位。比如,由於任何非0的值被JUMP指令認為是true。在它作用JUMPI指令的條件前,我們在不會清理這個布林值。

在上述設計準則之外,Solidity編譯器會在輸入資料載入到棧上後清理掉它們。

不同的型別,有不同的無效值的清理規則。

型別有效值無效值意味著
有n的成員的列舉型別0到(n - 1)異常(exception)
布林0或11
有符號整數sign-extended word當前靜默的包裝了結果,以後會以異常的形式丟擲來
無符號整數高位節是0當前靜默的包裝了結果,以後會以異常的形式丟擲來

內部機制 - 優化(Internals - The Optimizer)

Solidity是基於彙編優化的,所以它可以,同時也被其它程式語言所使用(譯者注:其它語言編譯為彙編)。編譯器會在JUMPJUMPDEST處拆分基本的指令序列為一個個的基本塊。在這些程式碼塊內,所有的指令都被分析。所有的對棧,記憶體或儲存的操作被記錄成由指令及其引數組成的一個個表示式,這些表示式又會指向另一個表示式。核心目的是找到一些表示式在任何輸入的情況下都恆等,然後將它們組合成一個表示式類。優化器首先嚐試在一系列已知的表示式中,找出來一些全新的表示式。如果找不到,表示式通過一些簡單的原則進行簡化,比如 constant + constant = sum_of_constants 或X * 1 = X。由於這一切是遞迴進行的,我們可以在第二項是一個更復雜的表達時,應用上述後續規則。對記憶體或儲存的修改,儲存的位置經常會被擦除,由此我們並不知道存的資料有什麼不同:如果我們首先寫入一個值x,再寫入另一個值y,這兩個都是輸入變數,第二個寫入時會覆蓋第一個,所以我們實際在寫入第二個值時,不知道第一個值是什麼了。所以,如果一個簡單的表示式x-y指向一個非0的常量,這樣我們就能在操作y時知道x記憶體儲的值。

在流程最後,我們知道哪一個表示式會在棧頂,並且有一系列的對記憶體或儲存的修改。這些資訊與基本的塊存在一起以方便的用來連線他們。此外,關於棧,儲存和記憶體配置的資訊會傳遞到下一個塊。如果我們知道所有JUMPJUMPI指令的目標,我們可以構建程式的完整的控制流程圖。如果有任何一個我們不知道目標的跳轉(因為目標是通過輸入引數進行計算的,所以原則上可能發生),我們必須擦除塊知識的輸入,因為他有可能是某個JUMP的目的地(譯者注:因為可能某個跳轉在執行時會指向他,修改他的狀態,所以他的推算狀態是錯誤的)。如果某個JUMPI被發現他的條件是常量,它會被轉化為一個無狀態的跳轉。

在最後一步,每個塊中的程式碼都將重新生成。在某個塊結束時,將生成棧上表示式的依賴樹,不在這個樹上的操作就被丟棄了。在我們原始程式碼中想要應用的對記憶體、儲存想要的修改順序的程式碼就生成出來了(被丟棄的修改被認為是完全不需要的),最終,生成了所有的需要在棧上存在的值。

這些步驟應用於每個基本的塊,如果新生成的程式碼更小,將會替換現有的程式碼。如果一個塊在分析期間在JUMPI處分裂,條件被證實為一個常量,JUMPI將可以基於常量值被替換掉,比如下述程式碼:

var x = 7;
data[7] = 9;
if (data[x] != x + 2)
  return 2;
else
  return 1;
簡化的程式碼可以被編譯為:
data[7] = 9;
return 1;


相關文章