場景
- 在
Rust
里不會出現野指針的情況,那么在C++
里能避免嗎?
說明
-
野指針是指指向無效內存地址的指針,訪問它會導致未定義行為,可能引發程序崩潰、數據損壞或安全漏洞。它是 C/C++ 等手動內存管理語言中的常見錯誤,而 Rust 通過編譯期檢查幾乎徹底消除了這一問題。
-
很遺憾,在
C++
里類成員指針變量是不會自動初始化的,它的指針地址是隨機的,可能為0
,可能為無效值。 而全部變量,C++
都會默認初始化,局部變量沒初始化就調用的話就會出現編譯警告。沒搞懂C++
標準為什么單單留著類成員變量不自動初始化的的問題。 -
在
C++11
開始,可以使用新語法給成員變量在聲明的時候直接賦值初始化。 這是開發自己手動做的工作,編譯器不會代辦。這種新語法還是減少了很多野指針的問題,比構造函數初始化列表方便多了。
class A
{
public:void* handle_ = NULL;int percent_ = 0;int64_t size_ = 0;
};
-
C
結構體,扁平數據結構可以用{0}
賦值初始化。 -
總結下,
C++
的變量初始化規則:
-
全局變量,在頭文件里聲明的或者在
.cpp
文件里聲明的都會被編譯器初始化,原始類型是0
,指針類型是NULL
。 -
類成員變量,靜態和非靜態的成員都不會被編譯器初始化。非靜態成員可以在聲明時就手動賦值初始化,而靜態非
const
成員必須在類外定義再次賦值初始化。 -
局部非靜態變量未初始化不能使用,會有編譯警告。
-
局部靜態變量會被編譯器自動初始化。
-
非指針類對象不需要賦值初始化,因為它會調用構造函數自動初始化。如果是類成員變量,那么在創建類實例的時候會自動初始化。如果是局部變量,那么也會在聲明時自動調用構造函數初始化。
例子
test-variable-init.cpp
#include <iostream>#include <memory>
#include <string>
#include <vector>
#include <stdint.h>
#include <assert.h>
#include "test-variable-init.h"using namespace std;class A;// 在頭文件里聲明了的全局變量,默認初始化.普通類型是0,指針類型是NULL;
int gLang;void* gWin;// 只在.cpp里聲明的變量,默認初始化.普通類型是0,指針類型是NULL;int gAdd;static long gCount;A* gA;struct RGB
{int r;int g;int b;
};class A
{
public:void* handle_ = NULL;int percent_ = 0;int64_t size_ = 0;RGB rgb_ = {0};string str_;// 沒有初始化string *str2_;private:static const bool bOk_ = false; // 可以直接賦值初始化static string *str3_; // 類靜態成員非const,不能直接賦值初始化。
};string* A::str3_ = nullptr;#define NATIVE_FREE(a,name) shared_ptr<void> a##name(a,[](void* data){ free(data); cout << "call free" << endl; })void TestVariableInit()
{assert(gLang == 0);assert(gWin == NULL);assert(gAdd == 0);assert(gCount == 0);assert(gA == NULL);// 方法的static變量,默認初始化.static int bRun;assert(bRun == 0);// 方法的非static變量,UB(未定義行為),需要手動初始化。int fNumber = 0; // 如果不手動初始化,編譯錯誤,使用了未初始化的局部變量。cout << "fNumber: " << fNumber << endl;int* fDay = NULL; // 如果不初始化,編譯錯誤,使用了未初始化的局部變量。//*fDay = 10; //RGB rgb2; // 如果不初始化,編譯錯誤,使用了未初始化的局部變量。//cout << "rgb2 r: " << rgb2.r << " g: " << rgb2.g << " b: " << rgb2.b << endl;RGB rgb = {0};cout << "rgb r: " << rgb.r << " g: " << rgb.g << " b: " << rgb.b << endl;// RGB *pRgb; // `pRgb` 如果不初始化,編譯錯誤,使用了未初始化的局部變量。RGB* pRgb = (RGB*)malloc(sizeof(RGB));memset(pRgb, 0, sizeof(RGB));cout << "pRgb: " << pRgb->r << endl;NATIVE_FREE(pRgb, pRgb);A a;cout << "a.size: " << a.size_ << endl;cout << "a.rgb r: " << a.rgb_.r << endl;// 未初始化,也不會編譯報錯。cout << "str2_ address: " << (int)a.str2_ << endl;
}int main()
{// 全部變量和靜態變量都存儲在全局的靜態儲存區。std::cout << "Hello World!\n";cout << "================ TestVariableInit ==============" << endl;TestVariableInit();
}
test-variable-init.h
#pragma onceextern int gLang;extern void* gWin;
輸出
Hello World!
================ TestVariableInit ==============
fNumber: 0
rgb r: 0 g: 0 b: 0
pRgb: 0
a.size: 0
a.rgb r: 0
str2_ address: 0
call free
參考
- 如何避免出現懸垂指針