# 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所指向的内存空间必须大于等于 srcnum个字节。

# 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)分配所需的内存空间,并返回一个指向它的指针。malloccalloc 之间的不同点是,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_ptrunique_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 newdelete

在堆上分配的内存是无名的,因此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[]必须成对使用,newmalloc分配的内存是连续的

# 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其他对象,也可以将其释放掉,还给系统。

(adsbygoogle = window.adsbygoogle || []).push({});

# 参考资料