AddressSanitizer/UBSan 實戰:一次數組越界是怎么被揪出來的
在 C++ 項目里,最讓人頭疼的一類問題就是內存相關的 bug。它們可能在測試階段沒暴露,一旦跑到線上,往往直接表現為崩潰或者莫名其妙的邏輯錯誤。更麻煩的是,有些錯誤代碼甚至能“正常運行”,但行為已經是未定義的了。
好在現代編譯器提供了一些強大的工具,比如 AddressSanitizer (ASan) 和 UndefinedBehaviorSanitizer (UBSan),用來幫助我們捕捉這些隱蔽的錯誤。下面我結合一個簡單的例子,看看它們是如何把 bug 揪出來的。
一個看似無害的 bug
假設我們有這樣一段代碼:
#include <iostream>
#include <vector>
int main() {
std::vector<int> data(5, 0);
for (int i = 0; i <= 5; ++i) {
data[i] = i;
}
std::cout << "done" << std::endl;
}直覺上,這個程序在循環時訪問了 data[5],顯然越界了。但問題在于,很多時候它不會直接崩潰,而是“安靜地”寫到了不屬于這個 vector 的內存。結果取決于運行環境,有時候你完全察覺不到。
這種 bug 在真實項目里就很危險,因為它可能破壞別的數據,直到很久以后才爆出來。
用 AddressSanitizer 抓出來
要啟用 ASan,只需要在編譯時加上參數:
g++ -fsanitize=address -g main.cpp -o main運行之后輸出大概是這樣的:
=================================================================
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014
WRITE of size 4 at 0x602000000014 thread T0
#0 0x400b1d in main /home/user/main.cpp:7
...
0x602000000014 is located 0 bytes to the right of 20-byte region
[0x602000000000,0x602000000014)
allocated by thread T0 here:
#0 0x7f2bda35 in operator new(unsigned long)
#1 0x4009f3 in std::vector<int>...可以看到,ASan 明確指出了“heap-buffer-overflow”,并且準確定位到越界發生的代碼行。甚至還提示了這個越界的寫操作剛好落在 std::vector 分配的 20 字節(5 個 int)的后面。
這類信息比傳統的 segfault 崩潰棧要詳細得多,非常適合排查這種內存越界的問題。
UBSan:另一類隱蔽錯誤
UBSan 關注的是 未定義行為,比如除以零、無效類型轉換、越界訪問等。它不會像 ASan 那樣攔截所有內存錯誤,但對于一些邏輯層面的未定義行為特別有用。
比如這段代碼:
#include <iostream>
int main() {
int x = -1;
unsigned int y = 1;
if (x < y) {
std::cout << "x < y" << std::endl;
}
}這里 x < y 的比較,其實是 有符號數和無符號數混合比較,C++ 標準規定會把 x 轉換為無符號類型,結果是個很大的數,條件判斷就變得出乎意料。
用 UBSan 編譯運行:
g++ -fsanitize=undefined -g main.cpp -o main程序會提示:
runtime error: signed integer overflow: -1 < 1 cannot be represented in type 'unsigned int'這就直觀地暴露了一個“潛在坑點”,讓我們能在開發階段及時修正。
兩者結合的效果
實際開發中,ASan 和 UBSan 可以一起使用:
g++ -fsanitize=address,undefined -g main.cpp -o main這樣既能捕捉內存越界等底層問題,也能發現一些未定義行為帶來的邏輯錯誤。
不過要注意的是,開啟 sanitizer 會增加運行時開銷(內存占用和性能),所以它更適合在開發和測試階段啟用,而不是在生產環境中常駐。
我自己在實際項目中的應用體會
結合自己的使用經驗,總結幾點:
- 盡早在開發階段開啟越早發現 bug,越容易修復。把 ASan/UBSan 打開做單元測試,往往能捕捉到很多肉眼看不到的邊界問題。
- 與 CI 流程集成在 CI 構建里加上 sanitizer 選項,可以保證每次提交都經過檢查。很多開源項目(比如 LLVM 自身)就長期啟用了 ASan。
- 不要依賴運氣內存 bug 有時候“運行得很正常”,但這并不代表沒問題。Sanitizer 給了我們一種系統化的方法來發現它們,而不是靠偶爾觸發的 crash。
- 知道它的局限ASan 并不能檢測所有的內存問題,比如內存泄漏(那要用 LeakSanitizer),或者多線程競爭(那要用 ThreadSanitizer)。但在大多數 C++ 項目里,它已經能解決很大一部分常見 bug。
我的感受是:Sanitizer 系列工具相當于給 C++ 項目加了一層“安全網”,很多模糊的未定義行為在它面前無處遁形。 如果你在寫 C++,真的值得把它們融入日常的開發和測試流程中。























