c++基础之堆栈分析、智能指针以及引用

1,073 阅读2分钟

image-20210318002457243.png

  • 栈区:向下为高地址,一般是栈顶指针往低地址移动,进行压栈。对于stack区,空间的开辟和释放由系统控制。

  • 堆区:向上为高地址。动态分配。对于heap区,空间的动态开辟和释放由程序员控制。

  • 常量区:图中少了,介于BSS和HEAP中间。

  • BSS:未初始化变量

  • GVAR:已经被初始化变量

  • TEXT:代码区

eg:

int a = 0;   //(GVAR)全局初始化区
int* p1;     //(bss)全局未初始化区
int main()   //(text)代码区
{
	int b = 1;                 //(stack)栈区变量
	char s[] = "abc";          //(stack)栈区变量
	int* p2 = NULL;            //(stack)栈区变量
	char* p3 = "123456"        //123456\0在常量区,p3在(stack)栈区
	static int c = 0;          //(GVAR)全局初始化区
	p1 = new int(10);          //(heap)堆区变量
	p2 = new int(20);          //(heap)堆区变量
	char* p4 = new char[7];    //(heap)堆区变量
	strcpy_s(p4,7,"123456");   // (text)代码区
	return 0;                  // (text)代码区
}

RAII(Resource Acquisizion Is Initialization): c++是唯一一个依赖RAII来做资源管理。依托栈和析构函数,对所有的资源包括堆内存在内进行管理。

image-20210318212246417.png

image-20210318215128307.png

使用指针经常会产生各种问题,如野指针、内存泄漏等等。我们可以采用两种更加安全的方案:

  • 使用智能指针
  • 使用引用
四种智能指针
auto_ptr:

在拷贝/赋值过程中,直接剥夺原对象对内存的控制权,转交给新对象,原对象指向nullptr,这个问题被称为“所有权转让”,这也是auto_ptr在c++17被废弃的原因之一。

auto_ptr<int> pI(new int(10));
cout << *pI << endl;

auto_ptr<string> languages[5] = {
    auto_ptr<string>(new string("C")),
    auto_ptr<string>(new string("Java")),
    auto_ptr<string>(new string("C++")),
    auto_ptr<string>(new string("python")),
    auto_ptr<string>(new string("Rust"))
};

for (int i = 0; i < 5; i++)
{
	cout << *languages[i] << endl; 
}
auto_ptr<string> pC;
pC = languages[2];

cout << "----------------\n";
cout << (languages[2].get() == nullptr ? "是" : "否") << endl; // 是
/*for (int i = 0; i < 5; i++)
{
	cout << *languages[i] << endl;
}*/

languages[2].reset(new string("Matlab")); // 重新赋值
for (int i = 0; i < 5; i++)
{
	cout << *languages[i] << endl;
}
unique_ptr:

专属所有权,所以unique_ptr管理的内存,只能被一个对象持有,不支持赋值和赋值。但是提供移动语义,使用std::move()完成控制所有的权的转移。

// 出作用域销毁堆空间,打断点到unique_ptr的析构函数,同时观察堆地址的内容的变化
{
    auto i = unique_ptr<int>(new int(10));
}
auto w = std::make_unique<int>(20);
cout << *(w.get()) << endl;
//auto w2 = w; // 编译错误
auto w2 = std::move(w); // w2获得内存所有权,w此时等于nullptr
cout << ((w.get() != nullptr) ? (*w.get()) : -1) << endl;
cout << ((w2.get() != nullptr) ? (*w2.get()) : -1) << endl;
shared_ptr:

通过引用计数共享一个对象。unique_ptr的局限性在于所有权,在同一时刻只能有一个智能指针指向我们的对象。

image-20210320123737325.png

当引用计数为0时,对象没有被使用才可以进行析构。

{
	auto wA = shared_ptr<int>(new int(10));
	{ 
		auto wA2 = wA;                                                 
		cout << ((wA2.get() != nullptr) ? (*wA2.get()) : -1) << endl;  // 10
		cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl;    // 10
		cout << wA2.use_count() << endl;                               // 2
		cout << wA.use_count() << endl;                                // 2
	}
	cout << wA.use_count() << endl; // 1,出了作用范围,wA2被销毁,引用计数减1
	cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl;        // 10
}
// 出了作用域,引用计数再减1,为0,堆内存被delete

通过std::move(),意味着原对象放弃对内存的所有权管理,原对象指向nullptr。

auto wAA = std::make_shared<int>(30);
auto wAA2 = std::move(wAA); // 此时wAA等于nullptr,wAA2引用计数为1
cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl;  // 30
cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl;    // -1
cout << wAA2.use_count() << endl;  // 1
cout << wAA.use_count() << endl;  // 0

问题:引用计数会带来循环引用(相互持有对方的引用)的问题,导致堆中的内存无法正常回收。

image-20210321002359590.png

class B;
class A
{
public:
	shared_ptr<B> pb;
	~A() 
	{
		cout << "~A()" << endl;
	}
};

class B
{
public:
	shared_ptr<A> pa;
	~B()
	{
		cout << "~B()" << endl;
	}
};

// test
void test() 
{
	shared_ptr<A> tA(new A());
	shared_ptr<B> tB(new B());
	cout << tA.use_count() << endl; // 1
	cout << tB.use_count() << endl; // 1
	tA->pb = tB;
	tB->pa = tA;
	cout << tA.use_count() << endl; // 2
	cout << tB.use_count() << endl; // 2
}
weak_ptr:
  • 被设计为与shared_ptr共同工作,观察者设计模式。weak_ptr只对shared_ptr进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也失效。
class BW;
class AW
{
public:
	shared_ptr<BW> pb;
	~AW()
	{
		cout << "~AW()" << endl;
	}
};

class BW
{
public:
	weak_ptr<AW> pa;
	~BW()
	{
		cout << "~BW()" << endl;
	}
};
// test
void test()
{
	shared_ptr<AW> tWA(new AW());
	shared_ptr<BW> tWB(new BW());
	cout << tWA.use_count() << endl; // 1
	cout << tWB.use_count() << endl; // 1
	tWA->pb = tWB;
	tWB->pa = tWA;
	cout << tWA.use_count() << endl; // 2
	cout << tWB.use_count() << endl; // 2
}

结果:

1
1
1
2
~AW()
~BW()
引用

一种特殊的指针,不允许修改的指针。

特性:

  1. 不存在空引用
  2. 必须初始化
  3. 一个引用媛媛指向它初始化的那个对象

引用可以被视作为变量的别名。

问题:

有了指针为什么还需要引用?

​	Bjarne Stroustrup(c++之父)的解释:为了支持函数的运算符重载;

有了引用为什么还需要指针?

​	Bjarne Stroustrup(c++之父)的解释:为了兼容C语言,不然和Java差不多了。

补充,关于函数传递参数类型的说明:

  1. 对内置基础类型(如int,double等)而言,在函数中传递时pass by value更加高效。
  2. 对OO面向对象中自定义类型而言,在函数中传递时pass by reference to const更加高效。