跳到主要內容

clang 避免 non-local static 物件初始化順序的方法

C++ 沒有定義 non-local static 物件在不同編譯單元 (translation unit) 之間初始化的順序, 所以要極力避免 non-local static 物件相互間的存取。

今天試用 clang 編譯程式, 發覺它有個不錯的選項: -Wglobal-constructors。用這選項編譯, 遇到有 non-local static 物件會依賴其它函式 (包含 constructor) 設值時, 會輸出 warning。

這裡引用 Address Sanitizer 提供的例子:

$ cat a.cc
#include <stdio.h>
extern int extern_global;
static int __attribute__((noinline)) read_extern_global() {
  return extern_global;
}
int x = read_extern_global() + 1;
int main() {
  printf("%d\n", x);
  return 0;
}

$ cat b.cc
int foo();
int foo() { return 42; }
int extern_global = foo();
$ clang++ a.cc b.cc && ./a.out
1
$ clang++ b.cc a.cc && ./a.out
43
$ clang++ a.cc b.cc -Weverything
a.cc:6:5: warning: declaration requires a global constructor [-Wglobal-constructors]
int x = read_extern_global() + 1;
    ^   ~~~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
b.cc:3:5: warning: declaration requires a global constructor [-Wglobal-constructors]
int extern_global = foo();
    ^               ~~~~~
1 warning generated.

由上可知, 編譯 a.cc 和 b.cc 的順序不同, 輸出的結果不同。加上 -Weverything 後, -Wglobal-constructors 有在第一時間抓出有問題的部份。附帶一提, clang 的錯誤訊息不止有標示錯誤的位置, 而且還是彩色的!

解決這個 warning 的方法, 和 Effective C++ Item 4 的說法一樣, 就是改用函式傳回 local static method, 這樣就會依執行的順序在執行期間初始化。不過實際情況稍微複雜了一點, 後述。

clang 另有一個參數 -Wexit-time-destructors, 會找出在結束程式時執行 destructor 的物件。雖然 destructor 執行的順序有明確的定義 (和初始化的順序相反), 不過開發者八成沒有考慮週全, 很容易在一連串 destructor 執行中用到已執行完 destructor 的物件。這個問題和 non-local static 物件初始化一樣棘手。

clang 的解決方案一樣單純: 「本來無一物, 何處惹塵埃。」統統不準用, 就不會出亂子。

$ cat p.cc
struct Point
{
  Point() {}
  ~Point() {}
  int x, y;
};


const struct Point& center();
const struct Point& center()
{
  static Point s_center;
  return s_center;
}
$ clang++ p.cc -c -Weverything
p.cc:12:16: warning: declaration requires an exit-time destructor [-Wexit-time-destructors]
  static Point s_center;
               ^
1 warning generated.

那要怎麼解決這個 warning 呢? 就是 new 一個物件, 並且不要釋放它。若懶得修改已經存取它的程式, 可以這麼做:

const struct Point& center()
{
  static Point& s_center = *new Point();
  return s_center;
}

在這兩個 warning 的夾擊下會少掉很多難以察覺的錯誤, 不過寫程式時也會有一點點不便。比方說需要用到常數字串時, 不能直接寫

const std::string kMyString = "...";

得改用

const char* kMyString = "...";

對於用 std::string 做為函式參數或 STL container 的物件, 得付出一點生成 std::string 的成本。

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

以使用 LevelDB 為例。 抓好並編好相關檔案,編譯方式見第三方函式庫附的說明:$ ls include/ # header files leveldb/ $ ls out-shared/libleveldb.so* # shared library out-shared/libleveldb.so@ out-shared/libleveldb.so.1@ out-shared/libleveldb.so.1.20* 下面的例子用 clang++ 編譯,這裡用到的參數和 g++ 一樣。 問題一:找不到 header$ clang++ sample.cpp sample.cpp:5:10: fatal error: 'leveldb/db.h' file not found #include "leveldb/db.h" ^ 1 error generated. 解法:用 -I 指定 header 位置 問題二:找不到 shared library$ clang++ sample.cpp -I include/ /tmp/sample-2e7dd8.o: In function `main': sample.cpp:(.text+0x1e): undefined reference to `leveldb::Options::Options()' sample.cpp:(.text+0x6f): undefined reference to `leveldb::DB::Open(leveldb::Options const&, std::string const&, leveldb::DB**)' sample.cpp:(.text+0x10c): undefined reference to `leveldb::Status::ToString() const' sample.cpp:(.text+0x7d0): undefined reference to `leveldb::Status::ToString() const' clang: error: linker command failed with exit code 1 (u…

熟悉系統工具好處多多

記一下以前很困擾, 現在秒殺的小事。 更新這篇的時候, 忘了函式庫用的 man page 裝在那個 package。以前就會想辦法 google, 運氣好一下會找到, 運氣不好會多找一會兒。 這回我想到新作法:$ strace -e open man 3 printf > /dev/null # 發現是讀 /usr/share/man/man3/printf.3.gz $ dpkg --search /usr/share/man/man3/printf.3.gz # 找到套件名稱 manpages-dev $ aptitude show manpages-dev # 確認描述符合, 收工

virtualbox 使用 USB 裝置

2012-12-16 更新 現在 (4.x 版) 似乎無需做任何設定, 只要有裝 Oracle VM VirtualBox Extension Pack, 在 VirtualBox 視窗右下角按 USB 的圖示, 再點目標裝置, 即可加入或移除該裝置 同一時間只有 host 或 guest 可擁有該裝置, 所以從 guest OS 移除, 相當於接回 host OS 目前 VirtualBox 只支援 USB 2.0 的插槽, 若偵測不到時, 注意一下是否為這個問題 有時拔拔插插, VirtualBox 會進入奇怪的狀態, 接上去 guest OS 無法連接且跳出 device is busy 的錯誤訊息。試看看拔除該裝置, 重開 guest OS (續上則) 若重開 guest OS 無效, 並且 host OS 已移除該裝置, VirtualBox 的 USB 清單卻仍顯示 "captured", 試看看拔除該裝置, 重開 host OS原文網路上搜一下, 比較多是 Ubuntu 當 host 的解法, 我的情況是 Win7 當 host, Ubuntu 當 guest。 這兩篇說明很詳細《Learn How to Set Up USB and Networking Options in VirtualBox》《幻影千瞳的部落格: VirtualBox 使用筆記(二):使用 USB 裝置》 現在的版本圖形介面很好用了, 不用像第二篇說的那樣用指令操作。這裡記下我的操作步驟: 關掉 guest OS 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB -> Enable USB 2.0 會出現訊息框, 說明要安裝 Oracle VM VirtualBox Extension Pack。下載後安裝它 host OS 插入 USB 隨身碟 在 VirtualBox 選單, 選擇 guest OS -> Settings -> USB, 點右邊有綠色 "+" 的 USB 頭的圖示, 選擇該 USB 隨身碟, 加入它的 filter 從 host OS 移除 USB 隨身碟 開啟 guest OS 插入 USB 隨身碟, 於是 guest OS 會自動偵測…