2011年10月30日 星期日

小心 C++ 的 non-local static 物件的初始順序

Java 有保證在第一次使用 class 內任何東西時, 會執行 class initialization 的程式。而 C++ 沒有保證不同編譯單元的 non-local static 物件的初始順序。

  • 編譯單元是指同一 object file 的原始碼和其引入的檔案
  • static 物件指不在 heap 也不在 stack 上的東西, 包含全域變數、namespace 上的變數、class 內 static member 等

這會導致一個可怕的事實: 使用另一個 object file 的 namespace 下的物件時 (這是滿常見的需求), 會導致未定義的行為。《Effective C++ 3/e》item 4 的建議解法是用函式存 local static 物件, 然後用函式取得該物件 (類似 singleton 的概念)。

用程式來說, 就是:

// Bad! Lead to an undefined behavior if clients access tfs.
FileSystem tfs;

// Good!
FileSystem& tfs() {
    static FileSystem fs;
    return fs;
}

附帶一提, 不同語言的規範差真多, 之前看 ARM 的組語, 才發覺之前只知道 x86 的東西, 而對 CPU 有些誤解。現在確信只學單一工具會有風險, 思維會受限於該工具, 誤以為那就是世界。人生有許多事也是如此, 想到莊子所說的話:

井蛙不可以語於海者,拘於虛也;﹔夏蟲不可以語於冰者,篤於時也;曲士不可以語於道者,束於教也。

體悟漸多後, 漸漸也不知要寫什麼心得才好。總之就是多看看, 時時提醒自己所知很窄, 別錯將一種方法當作只有這種方法。

2 則留言:

  1. 這tfs() 有點像 singleton pattern,我認為這種static只適用於 POD,或是contructor內沒有再depend on其他CRT或其他class。

    如果constructor內真的要用CRT的話,為何不用lazy-initialize?

    回覆刪除
  2. 呃... POD 和 CRT 是什麼的縮寫?

    至於 lazy initialization, 我個人偏好能不用就不用, 雖然方便, 但不容易掌握其行為。一堆 lazy initialization 串在一起時, 很難在腦中描繪清楚整個藍圖, 明白各元件的生命週期。也可能是我不熟這種作法, 才會有這種感覺吧。之前看 Django 原始碼時, 雖然明白這樣作可省下許多沒用到模組的初始化成本, 但覺得這種寫法不好讀, 而且可能也因方便亂用, 而將相依關係變更亂。

    回覆刪除

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...