动态内存基础(一)
动态内存基础
● 栈内存 堆内存 V.S.
– 栈内存的特点:更好的局部性,对象自动销毁
– 堆内存的特点:运行期动态扩展,需要显式释放

● 在 C++ 中通常使用 new 与 delete 来构造、销毁对象
int* fun()
{int res;return &res; //Error: Address of stack memory associated with local variable 'res' returned to caller [clang-analyzer-core.StackAddressEscape]
}
int* fun2()
{int* res2 = new int(2);return res2;
}
int main(void)
{fun2(); //可能引发的问题: 没有释放申请的指针,内存泄漏即memory leakageint* x = fun(); //悬空指针即dangling poniterint* x2 = fun2(); //OKint* y = new int(2); //y变量保存在栈内存里,new int(2)保存在堆内存里,y的值是new int(2)的地址std::cout << *y << std::endl; //解引用delete y; //y变量仍然保存在栈内存里,但是new int(2)已经释放掉了
}
● 对象的构造分成两步:分配内存与在所分配的内存上构造对象;对象的销毁与之类似
● new 的几种常见形式
– 构造单一对象 / 对象数组
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* y = new int; //缺省初始化int* z = new int[5]; //申请一个含有5个整型的数组,指向该数组的首地址int* u = new int[5]{ 1, 2, 3, 4, 5}; //列表初始化Since C++11std::cout << u[3];delete []z; //销毁该数组, []内并不需要指出数组大小delete []u;return a.exec();
}
– nothrow new
#include
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* y = new (std::nothrow) int(2); //如果内存申请不成功,会赋值0即NULLPTR给y,系统不会抛出异常if(y == nullptr) //OK{//}else //OK{std::cout << *y;//delete y;}return a.exec();
}
– placement new
#include
int* fun()
{char ch[sizeof(int)]; //栈内存int* y = new (ch) int(100);return y; //指向已被销毁的指针
}
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);char ch[sizeof(int)]; //申请了4个字节的栈内存, 类型变化: char[4] -> char* -> void*int* y = new (ch) int(100); //placement new不关心堆栈内存只关心可读写,ch必须是指针,必须足够大一存储要赋值的元素std::cout << *y;return a.exec();
}
– new auto
#include
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* y = new auto(3); //OK Since C++11int* z = new auto; //编译器先对new auto求值也不会根据int* z推导,Error: New expression for type 'auto' requires a constructor argumentreturn a.exec();
}
● new 与对象对齐
#include struct alignas(1024) Str{}; //对象对齐,与new无关,但不管在堆内存或栈内存上,都表现出相同的行为
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Str obj;std::cout << &obj << std::endl;Str* y = new Str;std::cout << y << std::endl;return a.exec();
}
● delete 的常见用法
– 销毁单一对象 / 对象数组
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* ptr = new int;int* arr = new int[5];delete ptr; //销毁单一对象delete []arr; //销毁对象数组return a.exec();
}
– placement delete
只销毁动态内存上的对象数据,并不释放该内存。应用于std::vector的成员函数void pop_back(),再次插入数据时不需要申请内存,提升了效率。
#include
struct Str
{~Str(){std::cout << "~Str()\n";}
};int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Str ch[sizeof(Str)];Str* ptr = new (ch) Str; //没有引入显式的销毁,因此不会打印~Str()ptr->~Str(); //引入了显式的销毁,打印~Str()。内存仍然在,没有释放并归还给系统return a.exec();
}

● 使用 new 与 delete 的注意事项
– 根据分配的是单一对象还是数组,采用相应的方式销毁
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* ptr = new int;delete[] ptr; //申请了单一对象,用销毁数组的方式销毁该对象,C++标准规定: 系统的行为未定义int* arr = new int[5];int* arr1 = new int[1]; //即使申请了一个元素的数组,但该对象的类型是一个数组delete arr; //申请了对象数组,用销毁单一对象的方式销毁该数组,C++标准规定: 系统的行为未定义delete[] arr1; //OKreturn a.exec();
}
– delete nullptr
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* ptr = nullptr;int* arr = nullptr;delete ptr; //OK,C++标准的一个额外的规定: 当指针指向nullptr或者0时,delete ptr什么都不做delete[] arr; //OK,C++标准的一个额外的规定: 当指针指向nullptr或者0时,delete[] arr什么都不做if(condition) //不管程序有没有运行到这一行,整个的程序都是合法的{//}delete[] ptr;return a.exec();
}
– 不能 delete 一个非 new 返回的内存
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int x; //栈内存上的变量delete &x; //行为未定义int* y = malloc(...);delete y; //delete一块malloc申请的内存,行为未定义int* z = new ...;free(z); //free一块new申请的内存,行为未定义int* u = new int[5];delete[] (u+1); //尝试删除非new返回的地址,系统的行为未定义return a.exec();
}
– 同一块内存不能 delete 多次
int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int* x = new int(3); //x是一个栈内存上的变量std::cout << x << std::endl; //x保存的值是一个堆内存上的地址delete x; //delete x;只是释放堆内存上的相应的内存,并不修改x的值std::cout << x << std::endl; //输出同上面的std::cout语句delete x; //系统报错Error: double free detectedx = nullptr;delete x; //OKreturn a.exec();
}
● 调整系统自身的 new / delete 行为
即new、new[]、 delete、delete[]运算符重载,参考operator new, operator new[]
– 不要轻易使用
动态库(或静态库)内部调整了new/delete行为,将申请的内存传给了库调用者并且希望库调用者释放这块内存,库调用者可能使用未调整的new/delete定义的行为,即库调用者试图将动态库(或静态库)在内存池里申请的内存归还给系统,程序的行为未定义。
参考
深蓝学院:C++基础与深度解析
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
