-
栈区:向下为高地址,一般是栈顶指针往低地址移动,进行压栈。对于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来做资源管理。依托栈和析构函数,对所有的资源包括堆内存在内进行管理。
使用指针经常会产生各种问题,如野指针、内存泄漏等等。我们可以采用两种更加安全的方案:
- 使用智能指针
- 使用引用
四种智能指针
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的局限性在于所有权,在同一时刻只能有一个智能指针指向我们的对象。
当引用计数为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
问题:引用计数会带来循环引用(相互持有对方的引用)的问题,导致堆中的内存无法正常回收。
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()
引用
一种特殊的指针,不允许修改的指针。
特性:
- 不存在空引用
- 必须初始化
- 一个引用媛媛指向它初始化的那个对象
引用可以被视作为变量的别名。
问题:
有了指针为什么还需要引用?
Bjarne Stroustrup(c++之父)的解释:为了支持函数的运算符重载;
有了引用为什么还需要指针?
Bjarne Stroustrup(c++之父)的解释:为了兼容C语言,不然和Java差不多了。
补充,关于函数传递参数类型的说明:
- 对内置基础类型(如int,double等)而言,在函数中传递时pass by value更加高效。
- 对OO面向对象中自定义类型而言,在函数中传递时pass by reference to const更加高效。