欢迎访问个人网络日志🌹🌹知行空间🌹🌹
0.程序中的内存
静态内存用来保存局部static 对象,类static 数据成员,以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static 对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static 对象的在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分的内存被称作自由空间或堆。程序用堆来存储动态分配的对象,即在程序运行时分配的对象。动态对象的生存期由程序来控制,即当动态对象不再使用时必须显式的销毁它。
1.C语言的内存管理
1.1malloc
包含头文件
#include <stdlib.h>
char *cp = malloc( 200 * sizeof(char) );
void *malloc(int num)
分配 num bytes大小的未初始化的内存
1.2realloc
Resizing memory allocated, 当malloc分配的内存不够使用时,使用 realloc 重新分配, 使用 malloc 分配的内存在使用结束的时候必须使用 free 函数进行释放,否则会造成内存泄漏。
#include <stdlib.h>
char *cp = malloc( 200 * sizeof(char) );
cp = realloc( cp, 100 * sizeof(char) );
free(cp);
1.3memset
在使用数组的时候经常因为没有初始化而产生“烫烫烫烫烫烫”这样的野值,俗称“乱码”,memset() 函数可以说是初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。
# include <string.h>
void *memset(void *s, int c, unsigned long n);
将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。memset() 的作用是在一段内存块中填充某个给定的值。因为它只能填充一个值,所以该函数的初始化为原始初始化,无法将变量初始化为程序中需要的数据。
memset 一般使用“0”初始化内存单元,而且通常是给数组或结构体进行初始化。一般的变量如 char、int、float、double 等类型的变量直接初始化即可,没有必要用 memset。
当然,数组也可以直接进行初始化,但 memset 是对较大的数组或结构体进行清零初始化的最快方法。
memset 函数将c 的值复制到s 指向的n 个字节的内存空间上,复制c 时会做类型转换,一次拷贝1个字节即unsigned char 类型,因此使用memset 初始化数组时有时会发生意想不到的结果。
1.4 memcpy
包含头文件
#include <string.h>
void* memcpy ( void *dest, const void *src, size_t num );
【形参】memcpy() 会复制 src 所指的内存内容的前 num 个字节到 dest 所指的内存地址上, memcpy() 并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
【返回值】返回指向 dest 的指针。注意返回的指针类型是 void,使用时一般要进行强制类型转换。
【备注】memcpy分配的内存在栈区或全局数据区,dest 指向的内存中的值可修改。dest 所指向的内存空间必须大于等于 src 的 num 个字节。
1.5 memmove
#include <string.h>
void * memmove ( void * destination, const void * source, size_t num );
【形参】destination与source 所指向的内存块至少要有num个字节 【返回值】返回指向 dest 的指针。注意返回的指针类型是 void,使用时一般要进行强制类型转换。 【备注】 从source 复制 n 个字符到 destination ,当内存不重叠时,其作用与memcpy 相同,如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。
1.6 calloc
C 库函数 void *calloc(size_t nitems, size_t size) 分配所需的内存空间,并返回一个指向它的指针。malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。
int *ip = (int *)calloc(2, sizeof(int));
ip[1] = 100;
cout << "Array made by calloc: " << ip[1] << endl;
2.C++中内存管理
在C++ 中,动态内存的管理是通过一对操作符来完成的。这两个运算符分别是:new 在动态内存中为对象分配空间并 返回一个指向该对象的指针,delete 接受一个的动态对象的指针,销毁该对象,并释放与之关联的内存。如下:
point op = point(2,3);
point *np = new point(2,3);
cout << "(x,y): " << np->x << "," << np->y << endl;
delete[] np;
手动管理动态内存的使用是十分困难的,有时候会忘记释放内存,这种情况下会产生内存泄漏;有时在尚有指针的引用 内存的情况下释放内存,这将导致引用非法内存的指针。
为了更好的使用动态内存,C++11 中提供了两种智能指针shared_ptr 和unique_ptr 来管理动态对象。智能指针同常规的指针,但其自动回收所管理的对象。两种智能指针的区别在于管理底层指针的方式,shared_ptr 允许多个指针指向同个对象,unique_ptr 则独占所指向的对象。标准库还定义了weak_ptr 的伴随类,它是一种弱引用,指向shared_ptr 所指向的对象。上述三个智能指针类定义在头文件memory 中。
2.1 智能指针
shared_ptr<int> p = make_shared<int>(10);
auto q(p);
shared_ptr<int> ptr;
未初始化的shared_ptr 会被初始化为一个空指针。 shared_ptr 使用引用计数器来统计有多少指针指向同个对象。
2.2 new 和delete
在堆上分配的内存是无名的,因此new 操作符号无法为其分配的对象命名,而是返回一个指向该对象的指针。用new 分配const 对象是合法的,一个动态分配const 对象必须进行初始化。shared_ptr 可以和new 一起使用,但不能将一个内置指针隐式转换成智能指针,只能通过直接初始化的形式。
const int *pci = new const int(1024);
vector<int> *p = new vector<int>{0, 2};
shared_ptr<int> p1 = new int(1024);
shared_ptr<int> p2(new int(1024));
为了防止内存耗尽,动态内存使用完毕后,必须将其还给系统。可以通过delete 表达式来释放内存。
delete p;
p = nullptr;
2.3 new[]与delete[]
默认情况下,new 分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。可以初始化动态分配的数组,可以对数组中的元素进行值初始化,方法是在大小之后跟一对() ,注意与malloc 的区别,malloc 只是分配内存空间,并没有进行初始化。可以用变量来确定要分配的对象的数目。
int *p = new int[10]();
int *pc = new int[2]{4,2};
size_t i = 10;
int *pp = new int[i];
delete[] p;
delete[] pc;
delete[] pp;
另:标准库还提供了一个可以管理new 分配的数组的智能指针unique_ptr 。
unique_ptr<int []> up(new int[10]{5,2,3});
cout << up[1] << endl;
up.release();
malloc与free, new与delete, new[]与delete[]必须成对使用
3.allocator类
new 操作符将内存分配和对象初始化组合在了一起,delete 将对象析构和内存释放组合在了一起。但是,当分配一块较大的内存时,通常需要在内存上按需构造对象,也就是将内存分配和对象构造解耦。
string* p = new string[100];
string s;
string* q = p;
while(cin >> s && s!="q!" && q!=p+100) {
*q++ = s;
}
delete[] p;
如上述代码,当输入q! 的时候程序就结束了,很可能大多数情况都不会输入100 个字符串,但是在使用new 的时候构造了100 的空字符串。而且,对于输入的元素,每个都被赋值了两次,一次是new 初始化时,一个是cin 输入时。
allocator 类在C++ 标准库memory 头文将中,用以将内存分配和对象构造分离开来。其分配的内存是原始的,没有构造的。
allocator<string> alloc;
size_t n = 3;
auto const pa = alloc.allocate(n);
auto qq = pa;
alloc.construct(qq++);
alloc.construct(qq++, 3, 'c');
alloc.construct(qq++, "hi");
for (int i = 0; i < n; i++) {
std::cout << "qq:" << *(pa+i) << std::endl;
}
while(qq!= pa) {
alloc.destroy(qq--);
}
alloc.deallocate(pa, n);
元素被destory 后,可以重新使用这部分内存来construct 其他对象,也可以将其释放掉,还给系统。
参考资料
|