C++ 内存模型(未完。。。)
数据存储
- 程序数据段
- 程序代码段
stack栈内存
- 栈内存属于执行期函数,编译时确定大小
- 函数执行时栈空间自动分配,结束时栈空间自动销毁
- 栈对象是线性分配,连续排列,没有内存碎片化效应
- 栈内存具有函数本地性,不存再多线程竞争
- 栈内存有大小限制,可能存在益处,linux默认未8MB,windows默认是1MB
- 栈内存使用对象或引用直接使用,管理复杂度较低
heap堆内存
- 堆内存具有全局共享特点,大小动态变化
- 堆对象分为时手动分配(malloc/new),对象释放时,手动释放内存(delete/free)
- 堆生对象链式分配,非连续排列
- 堆内存全局共享,存在多线程竞争的可能性
- 堆内存的大小没有栈内存严格限制,与机器内存总量和进程寻址空间有关
- 对内存使用指针简介访问,管理复杂度较高
堆与栈
- 栈内存分配快,内存连续,缓存友好,释放快
- 生存中期短,拷贝少(传值、返回值),栈内存性能比堆内存性能好
- 堆内存有更好的灵活性,但性能比栈内存差
- 堆内存再长期运行的程序中有内存碎片化的效应,会造成小快内存无法使用(内存泄漏),降低缓存使用效率
- 堆内存分配需要寻找合适内存大小的内存块,花费时间更多
- 栈对像可以使用RAII和指针以动操作避免拷贝的代价
值语义和引用语义
- 值语义:对象以值的方式直接存储,传值,返回值,拷贝等
- 引用语义:对象已引用或指针的方式间接存储,穿传值,返回值,拷贝传递的是引用或指针
- 值语义没有悬浮指针/引用,没有昂贵的释放操作,没有内存泄漏,没有数据竞争,但是值语义堆大对象拷贝代价高,不能支持虚函数多态
变量初始化
- 使用统一初始化:int a{50}; int a2 = {50};
- 赋值初始化:int a3 = 50;
- 构造初始化:int a4(50);
- 推荐使用统一初始化(列表初始化),可以避免隐式转换
指针容易出现的问题
- 内存泄漏——忘记释放已经申请的内存
- 悬浮指针——使用已经释放的内存,返回栈对象地址
- 重复删除——对已经删除的对象进行二次删除
- 删除非堆对象指针——对栈对像、全局/静态对象地址进行删除
- 使用空指针/使用失效的引用
int* glob;
void process(int* q){
glob = q;
}
void g1(){
int* p = new int{
7};
process(p);
delete p;
*glob = 9; //悬浮指针
}
void g2(){
int* p = new int{
7};
process(p);
delete p;
delete glob; //重复释放
}
void g3(){
int x = 7;
int* p = &x;
process(p);
delete glob;// 释放栈指针
}
对象内存空间
- 虚函数占用一个指针的大小
- 静态数据成员不参与类堆对象大小的计算
- 初始化顺序按照成员变量声明顺序
- 可以使用#pragma pack(4)来控制对象内存对齐的大小
特殊成员函数与三法则
- 三法则:析构函数、拷贝构造函数、赋值操作符,三者自定义其一,则需要同时定义另外两个(否则编译器自动生成的一般语义有误)
- 使用default让编译器自动生成;使用delete让编译器不要自动生成;
- 编译器自动生成的的拷贝/赋值时按字节拷贝,若出现指针等具有动态数据成员的对象,通常需要自己定义拷贝/赋值/构造行为
- 赋值操作符中的copy和swap是较为好的方法(注意赋值操作中需要避免自我赋值的情况)
- 普通类的成员函数其实相当远一个带有this指针的全局函数。静态成员函数其实不属于类,是一个全局函数,没有this指针;
性能指南
- 如果函数非常小,且时间很敏感,可已将其声明未inline
- 如果函数可能在编译器进行求值,将其声明为constexpr
- 尽量使用类初始化列表对类成员变量进行初始化
- explicit 避免隐式转换
- 使用重载避免引述转换
- 返回值优化(RVO)
vector 与 array
- vector在栈上,array在堆上
- vector使用时,建议设置大小,reserve(1000),避免vector的内存增长带来的拷贝
class MyClass {
public:
MyClass() = default;//强制生成默认的构造函数
~MyClass() = delete;//强制删除改构造函数
MyClass(const MyClass& tmp) = delete;
MyClass(const MyClass&& tmp) = delete;//移动拷贝
MyClass& operator=(const MyClass& tmp) = delete;
MyClass& operator=(const MyClass&& tmp) = delete;//移动赋值
};
class Point
{
public:
Point(int x, int y) : x_(x), y_(y) {
}
int x_;
int y_;
};
class Rectangle
{
public:
Point* left_up_;
int length_;
int width_;
//禁止构造函数隐式转换
explicit Rectangle(int date) {
}
Rectangle(int x, int y, int length, int width) : left_up_(new Point(x,y)), length_(length), width_(width) {
}
Rectangle(const Rectangle& rect) : length_(rect.length_), width_(rect.width_)
{
if (rect.left_up_ != nullptr)
left_up_ = new Point(*rect.left_up_);
else
left_up_ = nullptr;
}
Rectangle& operator=(const Rectangle& rect)
{
if (this == &rect)
return *this;
Rectangle temp(rect);
swap(left_up_, temp.left_up_);
this->length_ = temp.length_;
this->width_ = temp.width_;
return *this;
}
~Rectangle()
{
delete left_up_;
left_up_ = nullptr;
}
inline friend ostream& operator<<(ostream& os, const Rectangle& rect);
};
ostream& operator<<(ostream& os, const Rectangle& rect)
{
return os << "{" << rect.left_up_->x_ << "," << rect.left_up_->y_ << "," << rect.length_ << "," << rect.width_ << "}" << endl;
}
转载:https://blog.csdn.net/xiaofeilongyu/article/details/128353295
查看评论