场景
- 在
Rust
里不会出现野指针的情况,那么在C++
里能避免吗?
说明
-
野指针是指指向无效内存地址的指针,访问它会导致未定义行为,可能引发程序崩溃、数据损坏或安全漏洞。它是 C/C++ 等手动内存管理语言中的常见错误,而 Rust 通过编译期检查几乎彻底消除了这一问题。
-
很遗憾,在
C++
里类成员指针变量是不会自动初始化的,它的指针地址是随机的,可能为0
,可能为无效值。 而全部变量,C++
都会默认初始化,局部变量没初始化就调用的话就会出现编译警告。没搞懂C++
标准为什么单单留着类成员变量不自动初始化的的问题。 -
在
C++11
开始,可以使用新语法给成员变量在声明的时候直接赋值初始化。 这是开发自己手动做的工作,编译器不会代办。这种新语法还是减少了很多野指针的问题,比构造函数初始化列表方便多了。
cpp
class A
{
public:
void* handle_ = NULL;
int percent_ = 0;
int64_t size_ = 0;
};
-
C
结构体,扁平数据结构可以用{0}
赋值初始化。 -
总结下,
C++
的变量初始化规则:
-
全局变量,在头文件里声明的或者在
.cpp
文件里声明的都会被编译器初始化,原始类型是0
,指针类型是NULL
。 -
类成员变量,静态和非静态的成员都不会被编译器初始化。非静态成员可以在声明时就手动赋值初始化,而静态非
const
成员必须在类外定义再次赋值初始化。 -
局部非静态变量未初始化不能使用,会有编译警告。
-
局部静态变量会被编译器自动初始化。
-
非指针类对象不需要赋值初始化,因为它会调用构造函数自动初始化。如果是类成员变量,那么在创建类实例的时候会自动初始化。如果是局部变量,那么也会在声明时自动调用构造函数初始化。
例子
test-variable-init.cpp
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
cpp
#pragma once
extern 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