學習vlang程式設計(二)記憶體管理堆•棧篇
V首先透過使用值型別和字串緩衝區,來避免不必要的記憶體分配,大多數物件(大約90-100%)是由V的自動釋放引擎釋放的,編譯器在編譯過程中自動插入記憶體釋放的函式方法。剩下的小部分的物件透過引用計數釋放。開發人員不需要更改程式碼中的任何內容。“It just works”,就像在Python、Go或Java,除了沒有繁重的GC跟蹤或高成本的對每個物件的引用計數。V提供了記憶體管理的靈活性,能夠實現自動化,半自動化,和手動的記憶體管理。
堆和棧的基本概念
計算機的記憶體按用途可劃分為棧(stack)和堆(heap)。cpu有專門的處理棧的指令,資料進棧和彈出按照先入後出、後入先出的原則進行操作,管理起來簡單,開銷最小。v優先把各種資料結構的資料放到棧中進行處理,不需要進行額外的GC操作。但系統的棧空間太小,往往很難滿足程式的需求,所以開闢一塊大的記憶體進行資料的儲存就有必要了,這塊記憶體就稱為堆。
堆記憶體就需要合理的管理了。程式申請了一塊記憶體一直不釋放,造成堆記憶體資源緊張最後耗費盡了,計算機程式就崩潰了。一塊記憶體被不同的程式同時申請使用,造成資料干涉衝突,同樣會引起程式的崩潰。不合理的堆記憶體申請和釋放還會造成記憶體的碎片,使得記憶體的利用率和程式的效能大大降低。
V處理堆和棧的方法
出於效能考慮,V會盡量將物件放在棧上,但是在明顯需要的時候在堆上分配它們。例子:
//聲明瞭一個v的複合資料結構:struct,名稱為MyStruct,該結構的成員是n,型別是int(32位有符號整數)。
//宣告本身並不會進行記憶體的分配
struct MyStruct {
n int
}
//聲明瞭一個結構RefStruct,成員r的型別是結構MyStrunct的應用,用&表示引用。被引用的物件必須分配在堆上面。
struct RefStruct {
r &MyStruct
}
//main()是v的入口函式
fn main() {
//呼叫函式(),該函式有兩個返回值
q, w := f()
//列印輸出
println(‘q: $q。r。n, w: $w。n’)
}
//函式f()的定義和函式體
fn f() (RefStruct, &MyStruct) {
//初始賦值語句會分配記憶體給變數a
a := MyStruct{
n: 1
}
b := MyStruct{
n: 2
}
c := MyStruct{
n: 3
}
e := RefStruct{
r: &b
}
x := a。n + c。n
println(‘x: $x’)
return e, &c
}
這裡a被儲存在棧上,因為它的地址從未離開函式f() 。但是對b 的引用是返回的e的一部分,也引用了返回的c,因此, b 和c 將被堆分配。
手動控制堆的分配
V用編譯器指令強制將結構資料分配到堆上面:
[heap]
[
heap
]
struct
MyStruct
{
n
int
}
fn
main
()
{
m
:=
MyStruct
{}
//宣告r為可變變數
mut
r
:=
RefStruct
{
r
:
&
m
}
r
。
g
()
println
(
‘
r
:
$
r
’
)
}
//定義函式g(),該函式的接收者為結構RefStruct的變數
fn
(
mut
r
RefStruct
)
g
()
{
s
:=
MyStruct
{
n
:
7
}
r
。
f
(
&
s
)
`
}
fn
(
mut
r
RefStruct
)
f
(
s
&
MyStruct
)
{
r
。
r
=
s
}
如果不用[heap]宣告,上面的程式碼中宣告r。r=s語句就會出現編譯錯誤。這裡宣告struct MyStruct 的[heap]屬性是解決這種困境的方法,它指示編譯器
總是
分配物件MyStruct 在堆上。這樣,即使在函式方法 g() 返回之後,對變數s 的引用仍然有效。編譯器考慮到MyStruct 物件總是分配在堆上,在檢查 f() 時分配,並允許將對s 的引用賦值給r。r欄位。
小 結
記憶體棧和堆的管理方法和開銷是不同的
複雜和資料量大的資料結構應分配在堆上
分配在堆上的物件可以被引用,或者說被引用的物件必須分配在堆上
v可以用編譯器指令強制分配資料物件在堆上。
參考文獻
Vlang文件
https://
github。com/vlang/v/blob
/master/doc/docs。md