Yung-Yu Chen
banner
yungyuc.bsky.social
Yung-Yu Chen
@yungyuc.bsky.social
adjunct assistant professor at NYCU CS :: teaching https://yyc.solvcon.net/en/latest/nsd/ at 7am :: PSF fellow
也因此,move on return 的效能會比較差,因為還會用 move constructor 多建立一個物件。

C++ 是很恐怖的,為了速度,它可以違背自己的語意。但看熟了,也就習慣了。
May 6, 2025 at 1:32 PM
我們寫 C++ 是為了至高的執行速度。而 C++ 自己為了執行速度,也會作非常邪惡恐怖的事。在必要的時候,它會跳過 copy constructor 不執行!

這叫作 copy elision (或 return value optimization)。你明明實作了物件的 copy constructor,也在函式內建立自動物件然後傳回,但 copy elision 允許 C++ compiler 編出不呼叫 copy constructor 的程式。
May 6, 2025 at 1:32 PM
std::shared_ptr 會使用 reference counting (refcount) 計算 owner (reference),當 refcount 從 1 降到 0 的時候銷毀物件。而 owner 超過 1 的時候,就只會操作 refcount,不會呼叫物件的建構與解構子。這樣就解除了 std::unique_ptr 最多只有 1 個 owner 的限制。
May 1, 2025 at 7:06 AM
std::shared_ptr 是大方的 smart pointer,不管給幾個 owner 都沒有問題,它會自己計算 owner 的數量。只要 owner 不為 0,shared pointer 會保證被指涉的物件處於存活可用的狀態。
May 1, 2025 at 7:06 AM
雖然這的確解決了缺乏 ownership 資訊的問題,但寫起來並不方便,物件有可能用一用就會不見。反過來說,在建立物件的時候就必須要考慮清楚 ownership,這也不能說不是好處。

如果使用 std::shared_ptr,就可以解開 std::unique_ptr 的限制。當然,更多的功能就會帶來更多的問題。
April 30, 2025 at 12:20 PM
C++ raw pointer 對設值與取值完全不作限制,所以無法承載 ownership 的資訊。而 std::unique_ptr 透過僅允許 0 或 1 個 owner,能在仍然只使用一個 raw pointer 的情況下實作 ownership。

代價是在傳遞 unique_ptr 的時候,ownership 會一起被傳走。
April 30, 2025 at 12:20 PM
std::unique_ptr 只允許 0 或 1 個 owner,所以在預設的情況下 (使用 std::default_delete),它就只是一個 raw pointer,所以幾乎沒有 overhead。
April 29, 2025 at 2:00 PM
std::unique_ptr 透過關掉 copy constructor 與 copy assignment operator,實作僅允許 0 或 1 個 owner 的行為。

因為不能複製,所以若不是空指標,就是唯一的 owner。單純而有效。
April 29, 2025 at 5:01 AM
STL container 還提供 swap(),是個老派但好用的記憶體管理功能。建立一個匿名 (anonymous) 物件以後,呼叫它的 swap() 函式和 vec1 交換,就可以把 vec1 內的資料清除。

另外,在 C++11 以後,STL 開始支援 move semantics,允許在 container 之間移動資源,可以在必要的時候減少複製昂貴 (消耗資源) 的物件。但 swap() 還是很有用的寫法。也許一開始看會覺得奇怪,但熟悉了 C++ 物件生命周期以後,可以用它寫出清楚易懂的程式。
April 25, 2025 at 11:46 PM
實驗就拿最簡單的 std::vector 來作。建立一個空的 std::vector<size_t>,取名為 vec1,然後往裡面放 1,024 個元素,會發現實際配置的記憶體是超過 8,192 位元組的,但配置減去釋放的位元組數剛好足夠最後留在 vec1 裡面的資料使用。

至於為什麼會多配置那麼多記憶體,讀一下 std::vector 的實作就會知道。不難讀,而且每個編譯器都會附原始碼。
April 25, 2025 at 11:46 PM
STL container 在配置記憶體的時候,是不允許使用者從外部用 new/delete 控制的。所以一開始接觸 C++ 的人看到 std::vector 的時候會搞不清楚它是怎麼配置記憶體的,也可能會搞錯,寫出 std::vector<T> * vec = new std::vector<T> 這種程式 (千萬不要這樣寫)。

STL container 的記憶體是由 allocator 控制的,我們可以寫個簡單的 STL allocator 來作一下實驗,紀錄 STL container 配置以及釋放的記憶體。Allocator 內部可以用 new/delete,但也可以不用。
April 25, 2025 at 11:46 PM
calloc 看似無用,但暗藏玄機。配置一大塊記憶體的時候,malloc 和 calloc 都會去和作業系統要 memory map (mmap)。從別的行程 (process) 配置記憶體過來的時候,作業系統需要把資料清成零,此時 calloc 就不必再耗費時間清零。

在呼叫 malloc 的場合,外部無從得知記憶體是從堆積 (heap) 或 mmap 配置而來,因此一定需要再作一次 memset(0),才能確定。

在配置大塊全零值記憶體的時候,calloc 會比 malloc + memset 更快。

愈了解電腦系統的運作方式,程式可以寫得愈好。
April 24, 2025 at 7:31 AM
記憶體管理是很基礎的系統程式。一般程式員會覺得記憶體管理是作業系統的事情,但其實至少一半的工作是交給程式連結 (link) 的 C 標準程式庫負責的,也就是 malloc, calloc, realloc, aligned_alloc, 和 free。

malloc/free 就能處理大部分的記憶體管理工作,但其它 API 也有用處,例如 aligned_alloc 能用來配置 SIMD 所需要的對齊 (aligned) 記憶體區塊。
April 24, 2025 at 7:31 AM
用 placement new 建構的物件,不能直接呼叫 delete。越俎代庖的結果會發生記憶體錯誤,例如 double free。

原因顯而易見。delete 會呼叫解構子,再釋放記憶體。但 placement new 建構的物件,記憶體不是自己配置的,所以不能自己釋放。
April 22, 2025 at 10:57 PM
在 scalar new, array new[] 之外,C++ new expression 也支援 placement new 。它接受一個指標,然後呼叫物件的建構子,初使化指標所指定的記憶體。也就是說,placement new 會在指定的記憶體位址呼叫物件的建構子,但不會先呼叫 ::operator::new 配置記憶體。

在跨模組的場合,會使用 placement new建構物件。因為所建構的物件位於其它模組所管理的記憶體之上,所以不能在建構之前自行配置記憶體。像 boost.python 和 pybind11 這種動態語言的包裝工具,就需要這種用法。
April 22, 2025 at 10:57 PM
用 array delete 處理被 scalar new 的物件也會有相似的記憶體錯誤。總之,scalar new 必須搭配 scalar delete,array new[] 必須搭配 scalar delete[],不可混用。
April 22, 2025 at 4:22 AM
這是因為 array new 會在記憶體前面多作一塊 padding 存放物件數量,以便 array delete 讀回來呼叫正確數量的解構子。
April 22, 2025 at 4:22 AM
配置記憶體、建構子、解構子、釋放記憶體,這四個步驟的順序絕不能搞錯,不然會造成程式的記憶體存取錯誤。

如果直接在 stack 上建構物件而沒有使用動態記憶體配置,就不需要 new/delete。automatic storage duration 會確保脫離 scope 的時候呼叫物件的解構子。
April 21, 2025 at 11:35 AM
C++ 的 new/delete 是很有深度的。先從簡單的 scalar new/delete 聊起。

為了在 dynamic storage duration 的場合處理物件的建構子 (constructor) 與解構子 (destructor),C++ 提供一對 new/delete expression。new (expression) 負責呼叫 ::operator::new 配置記憶體,然後再呼叫物件建構子。delete (expression) 則先呼叫物件解構子,再呼叫 ::operator::delete 釋放記憶體。
April 21, 2025 at 11:35 AM
在 C++ 裡面使用 template,一份程式碼就可以實作各種數值型別的計算 (主要是單精準度與倍精準度浮點數兩種)。但是 Python 看不懂 C++ template,包到 Python 以後沒辦法泛型。寫功能的時候沒問題,但測試的時候麻煩。

相似的型別會需要相似的單元測試程式。寫起來重複乏味,但可以忍受。真正麻煩的是這些測試程式長得太像,容易多改、漏改,或是改錯。

這可以解。設計一組基底類別,為相似的型別提供別名即可。

測試用的 Python 程式碼寫在衍生類別裡面,就會回復到只有一套。也就是說,由基底類別負責管理型別與精度的差異。
April 13, 2025 at 12:51 AM
愈容易記住的程式品質愈高。

為了掌握複雜的觀念和邏輯,人腦需要抓住一些記憶點。看到熟習的程式的時候,會回想那些重點。這些重點的內容和編排方式就決定了程式的結構。

std::vector 是個蠻好的例子。一旦知道要用三個指標實作 std::vector,就會把它的行為特性印到腦袋裡面。這三個指標是很好的實作,很難不用,也很容易用到其它地方。這種就是好程式。
March 26, 2025 at 11:35 PM
每隔一陣子社群網路上就會流行 committers top 的各國 GitHub 用戶排名,最近好像又有一波。看了一下,到 3/20 為止的一年間,313 個 commit 在台灣的 40,586 個使用者中排名 171。

也就是說平均每一兩天往 GitHub 送 (public) commit 的使用者,在台灣不到兩百個。這樣太少。

起碼來說,先不要談品質,資訊系學生送 patch 的密度不能更低,不然程式編寫的練習量是不夠的。
March 25, 2025 at 4:40 AM
作事情是需要驅動力的。這其實也是老生常談,但寫程式的人總是想把所有事情自動化,很容易忘記。

不管要作什麼事情,寫程式也一樣,記著一定要盯著看。沒有人盯著看,事情一定作不好。就算是簡單的動作,不專心也容易出錯。複雜的工程業務更是如此。需要有人驅動 (drive) 大家作事,效率才會高。

有在健身的人一定會同意,有教練在盯和自己練習,效率天差地遠,所以教練課雖然貴,大家還是願意付錢,它有這個價值。

如果不強制驅動,工作不會有效率。人性趨向省錢省事,能坐就不想站,能躺就不想坐。如果沒有人管,大家一定會便宜行事。就好像不抓違規停車的地方,就算停車場就空在旁邊,大家仍然會停在紅線上。
March 23, 2025 at 2:31 AM
處理效能最佳化的時候,不要先想,而要等到 profile 結果出來了以後再思考。

強者我朋友最近送了一個 patch,幫一段程式加速十八倍。

改動的程式其實很少,重點是拿掉 accessor,直接操作指標,這就能消除很多 overhead。然後把迴圈拆成兩個,也有點幫助。

這種 tight loop 寫得多了以後,其實蠻容易直接寫成快速的版本。不過偶爾還是會忘記,所以也不必逼自己人腦最佳化。先寫個版本,跑一下 profile,很快就能找到 performance hotspot,然後對症下藥,不管對程式熟不熟,都一樣有效。
March 20, 2025 at 2:27 PM
CLion 看不懂用 macro 生的 members 🤣
March 20, 2025 at 12:42 AM