Skip to content

Instantly share code, notes, and snippets.

@KmolYuan
Last active June 12, 2022 06:52
Show Gist options
  • Save KmolYuan/916db2e300b257361fc9e36455598e5e to your computer and use it in GitHub Desktop.
Save KmolYuan/916db2e300b257361fc9e36455598e5e to your computer and use it in GitHub Desktop.

指派 (Assignment)

指派行為是程式語言中重要的一環,除了常見的 = 運算子以外,表達式 (expression) 之間的傳遞也是指派的一種,例如呼叫函式 (functions)、運算子 (operators) 等。

res = foo(arg1, arg2) + arg3

變數 (variables) 在控制流程 (control flow) 上會經常性的重複引用,變數在實作上會跟記憶體連結,有幾種辦法:

  • 變數代表記憶體 (memory),最直覺的表示方式,變數刪除時記憶體也會跟著刪除。
  • 變數代表引用計數器 (reference counter),類似於指標,但是會以引用數量來決定刪除時機。
  • 變數代表智慧指標 (smart pointer),智慧指標與指向的內容一起建立和刪除,傳遞時僅出借指標。如果保證記憶體安全,效果同於引用計數器。

以下介紹不同的傳遞方式。

Copy

程式語言:C, C++

MyType a();
auto b = a;
// 複製,b 與 a 的記憶體不同

變數代表記憶體,指派時永遠使用複製,徹底分離與前一個變數的關係。然而這樣會導致過度的複製開銷,有兩種方法解決:

  • 使用指標 (pointer)參照 (reference),兩者本質一樣都是指標,語意不同。差別在於指標可懸掛 (hang),參照只能從現有變數建立。
  • 使用複製減少 (copy reduction) 最佳化,這個功能通常會自動啟用。依賴於編譯器決定變數是否複製,可從往後的修改行為得知。

問題

必須注意使用指標時的記憶體安全性 (memory safty)、多執行緒的資料競爭 (data racing) 問題。

Move

程式語言:Rust

let a = MyType::new();
let b = a;
// 移動,b 就是 a(不能再使用)

變數代表記憶體,指派時會移動到下一個變數,上一個變數會無法再使用。這麼做會強迫顯式複製 (explicit copy) 和強迫使用參照。並且能夠提前結束變數的生命週期,因為變數會被移動到呼叫的函式內。這麼做的優點是複製與引用方式一目了然,但也可能需要理解更多語意。

let a = MyType::new();
let b = foo(a.clone() + &c);
// 顯式複製和取址

不過對於輕量的原始類型 (primitive type),可以通過啟用自動複製功能來避免顯式複製。如果一個變數引用兩次以上,代表最後一次之前都要求自動複製。而指標需要看生命週期的時間決定是否收回引用(刪除指標)。

let a = MyType::new();
let a_ref = &a;
drop(a);
// a 和 a_ref 都被刪除,儘管 a_ref 可以自動複製

問題

要另外標記自動複製的類型。儘管易於閱讀,設計程式語言時仍必須要有很多配套措施,增加學習門檻。

Reference Counter

程式語言:Python, Java, JavaScript, Ruby, C#, Go, ...

a = MyType()
b = a
# 增加引用,b 就是 a,「MyType」的實例有 2 個引用

變數代表引用計數器,是一種十分受歡迎的概念。當記憶體位置被劃分後,只需要搭配一個計數器計算引用數量,便可以避免複製,又能有類似移動的效果。引用計數器只能用計數器堆棧來減少計數器(語意上的堆棧),其他手動改變計數器的行為都無法確保記憶體安全。另外要注意淺層複製 (shallow copy) 的行為。

問題

需要特別注意龐大的資料是否會長期佔用記憶體,清除引用時也較多顧慮。多執行緒下需要設計獨立的讀寫鎖(語言原生或另外製作),以防止資料競爭。

Copy on Write (CoW)

程式語言:MATLAB, Fortran (pointer)

a = MyType;
b = a;
% 增加引用,b 就是 a,「MyType」的實例有 2 個引用

變數代表引用計數器,然而為了產生複製的效果,會在修改時複製,這就可以讓其中的引用維持原本的數據。好處是在開發者不確定的情況下,會比單純複製還節省記憶體。

如果變數代表智慧指標,智慧指標的複製開銷同於 usize 的大小(端看 32 bit 跟 64 bit 而定)。

問題

同引用計數器,另外需要注意無法從引用修改原始資料。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment