# 0.程序中的内存
静态内存用来保存局部static
对象,类static
数据成员,以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static
对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static
对象的在使用之前分配,在程序结束时销毁。
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分的内存被称作自由空间或堆。程序用堆来存储动态分配的对象,即在程序运行时分配的对象。动态对象的生存期由程序来控制,即当动态对象不再使用时必须显式的销毁它。总结如下:
- 栈区(stack),由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等,栈的最大存储是有大小的,如linux上是
8Mb
,超过这个大小会栈溢出stackoverflow
。 - 堆区(heap),一般由程序员分配释放,堆区的内存在整个进程中都能使用,例如函数中
new
出来的对象,可以被函数外引用,但new
出来的内存必须记得清除,否则有可能导致segment fault
。堆内存没有大小限制,直到内存占满。 - 全局区(静态区,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) );
/* suppose you want to store bigger cp */
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
接受一个的动态对象的指针,销毁该对象,并释放与之关联的内存。如下:
// 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
shared_ptr<int> p = make_shared<int>(10);
auto q(p); // q是p的copy,会递增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); // error
shared_ptr<int> p2(new int(1024)); // right
为了防止内存耗尽,动态内存使用完毕后,必须将其还给系统。可以通过delete
表达式来释放内存。
delete p;
p = nullptr; // 释放后重置指针
# 2.3 new[]与delete[]
默认情况下,new
分配的对象,不管是单个分配的还是数组中的,都是默认初始化的。可以初始化动态分配的数组,可以对数组中的元素进行值初始化,方法是在大小之后跟一对()
,注意与malloc
的区别,malloc
只是分配内存空间,并没有进行初始化。可以用变量来确定要分配的对象的数目。
// allocated
int *p = new int[10]();
int *pc = new int[2]{4,2};
size_t i = 10;
int *pp = new int[i];
// free allocated
delete[] p;
delete[] pc;
delete[] pp;
另:标准库还提供了一个可以管理new
分配的数组的智能指针unique_ptr
。
unique_ptr<int []> up(new int[10]{5,2,3});
cout << up[1] << endl; // 2
up.release(); // 自动调用delete[] up;
malloc与free, new与delete, new[]与delete[]必须成对使用,new
和malloc
分配的内存是连续的
# 3.allocator类
new
操作符将内存分配和对象初始化组合在了一起,delete
将对象析构和内存释放组合在了一起。但是,当分配一块较大的内存时,通常需要在内存上按需构造对象,也就是将内存分配和对象构造解耦。
string* p = new string[100]; // 构造了`100`个空string
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; // 可以分配string的allocator对象
size_t n = 3;
auto const pa = alloc.allocate(n); // 分配2个未初始化的string
auto qq = pa; // 保持数组首地址
alloc.construct(qq++); // 空字符串
alloc.construct(qq++, 3, 'c'); // ccc
alloc.construct(qq++, "hi"); // 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
其他对象,也可以将其释放掉,还给系统。
# 参考资料
← C++中的指针与函数 C++中的左值和右值 →