C++ 中的容器很好用,比如 vector, map 等,可以动态扩容,自己管理内存,不用用户关心,但是在某些极端情况下,如果内存比较紧张的情况下,可能用户对于这些容器自己的管理规则(主要是释放规则)就不太满意了。
1. erase, clean。
通常在使用 map 的时候直接通过 erase,或者 clean 方法就可以删除数据,如果结合智能指针,用户不用关心内存的问题。但是需要注意的是,调用 erase 或者 clean 方法后,内存并没有立马释放,你仅能认为在某个时刻会自动释放。
下面的代码模拟了这一现象。第一个循环创建了一个 map,第二个循环清理数据。
#include <opencv2/opencv.hpp>。
#include <map>。
using namespace cv;。
using namespace std;。
int main(void)
map<int, shared_ptr<Mat>> m;。
for (int i = 0; i < 1000; ++i)。
{
const char* imagename = "D:\\Code\\test\\image\\t\\bb.png";。
Mat img = imread(imagename);。
shared_ptr<Mat> p = make_shared<Mat>(img);。
m.insert(pair<int, shared_ptr<Mat>>(i, p));。
}
for (int i = 0; i < 1000; ++i)。
{
m.erase(i);
}
cvWaitKey(0);
return 0;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
创建 map 的过程中内存占用持续上涨。
在这里插入图片描述
但是清理数据过程中并没有立即释放内存。
在这里插入图片描述
但是继续运行,内存被释放了。
在这里插入图片描述
2. 清理内存
在使用容器的过程中,如果存在频繁的插入/清除数据的动作,那么很容易就会造成内存碎片。对于 vector(包括string)可以通过 swap 等手段(参考C++11 中 vector 的基本操作及使用注意事项)来立即释放内存,但是 map 好像并没有(参考:知乎回答)。
Linux 下的 glibc 提供了 malloc_tim() 函数,但是 windows 下并没有。通过 malloc_trim() 函数可以清理内存碎片。
如何查看堆内内存的碎片情况 ?
glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况。
struct mallinfo { 。
int arena; /* non-mmapped space allocated from system */ 。
int ordblks; /* number of free chunks */ 。
int smblks; /* number of fastbin blocks */ 。
int hblks; /* number of mmapped regions */ 。
int hblkhd; /* space in mmapped regions */ 。
int usmblks; /* maximum total allocated space */ 。
int fsmblks; /* space available in freed fastbin blocks */ 。
int uordblks; /* total allocated space */ 。
int fordblks; /* total free space */ 。
int keepcost; /* top-most, releasable (via malloc_trim) space */ 。
};
/*返回heap(main_arena)的内存使用情况,以 mallinfo 结构返回 */。
struct mallinfo mallinfo();。
/* 将heap和mmap的使用情况输出到stderr*/。
void malloc_stats();。
可通过以下例子来验证mallinfo和malloc_stats输出结果。
#include <stdlib.h> 。
#include <stdio.h> 。
#include <string.h> 。
#include <unistd.h> 。
#include <sys/mman.h> 。
#include <malloc.h>。
size_t heap_malloc_total, heap_free_total,mmap_total, mmap_count;。
void print_info() 。
{
struct mallinfo mi = mallinfo(); 。
printf("count by itself:\n"); 。
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\tmmap_total=%lu mmap_count=%lu\n",。
heap_malloc_total*1024, heap_free_total*1024, heap_malloc_total*1024-heap_free_total*1024,。
mmap_total*1024, mmap_count); 。
printf("count by mallinfo:\n"); 。
printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\tmmap_total=%lu mmap_count=%lu\n",。
mi.arena, mi.fordblks, mi.uordblks, 。
mi.hblkhd, mi.hblks); 。
printf("from malloc_stats:\n"); 。
malloc_stats(); 。
#define ARRAY_SIZE 200 。
int main(int argc, char** argv) 。
{
char** ptr_arr[ARRAY_SIZE]; 。
int i;
for( i = 0; i < ARRAY_SIZE; i++) 。
{
ptr_arr[i] = malloc(i * 1024); 。
if ( i < 128) //glibc默认128k以上使用mmap。
{
heap_malloc_total += i; 。
}
else 。
{
mmap_total += i; 。
mmap_count++; 。
}
}
print_info(); 。
for( i = 0; i < ARRAY_SIZE; i++) 。
{
if ( i % 2 == 0) 。
continue; 。
free(ptr_arr[i]);。
if ( i < 128) 。
{
heap_free_total += i; 。
}
else 。
{
mmap_total -= i; 。
mmap_count--; 。
}
}
printf("\nafter free\n"); 。
print_info(); 。
return 1;
该例子第一个循环为指针数组每个成员分配索引位置 (KB) 大小的内存块,并通过 128 为分界分别对 heap 和 mmap 内存分配情况进行计数;
第二个循环是 free 索引下标为奇数的项,同时更新计数情况。通过程序的计数与mallinfo/malloc_stats 接口得到结果进行对比,并通过 print_info打印到终端。
下面是一个执行结果:
count by itself: 。
heap_malloc_total=8323072 heap_free_total=0 heap_in_use=8323072 。
mmap_total=12054528 mmap_count=72 。
count by mallinfo: 。
heap_malloc_total=8327168 heap_free_total=2032 heap_in_use=8325136 。
mmap_total=12238848 mmap_count=72。
from malloc_stats: 。
Arena 0:
system bytes = 8327168 。
in use bytes = 8325136 。
Total (incl. mmap): 。
system bytes = 20566016 。
in use bytes = 20563984 。
max mmap regions = 72 。
max mmap bytes = 12238848。
after free
count by itself: 。
heap_malloc_total=8323072 heap_free_total=4194304 heap_in_use=4128768 。
mmap_total=6008832 mmap_count=36。
count by mallinfo: 。
heap_malloc_total=8327168 heap_free_total=4197360 heap_in_use=4129808 。
mmap_total=6119424 mmap_count=36。
from malloc_stats: 。
Arena 0:
system bytes = 8327168 。
in use bytes = 4129808 。
Total (incl. mmap): 。
system bytes = 14446592 。
in use bytes = 10249232 。
max mmap regions = 72 。
max mmap bytes = 12238848。
由上可知,程序统计和mallinfo 得到的信息基本吻合,其中 heap_free_total 表示堆内已释放的内存碎片总和。
如果想知道堆内究竟有多少碎片,可通过 mallinfo 结构中的 fsmblks 、smblks 、ordblks 值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节,80~512 字节,512~128k。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。
不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!
linux系统用户空间中动态申请内存的函数为malloc (),这个函数在各种操作系统上的使用都是一致的,malloc ()申请的内存的释放函数为free()。对于Linux而言,C库的malloc ()函数一般通过brk ()和mmap ()两个系统调用从内核申请内存。由于用户空间C库的malloc算法实际上具备一个二次管理能力,所以并不是每次申请和释放内存都一定伴随着对内核的系统调用。比如,代码清单11.2的应用程序可以从内核拿到内存后,立即调用free(),由于free()之前调用了mallopt(M_TRIM_THRESHOLD,一1)和mallopt (M_MMAP_MAX,0),这个free ()并不会把内存还给内核,而只是还给了C库的分配算法(内存仍然属于这个进程),因此之后所有的动态内存申请和释放都在用户态下进行。另外,Linux内核总是采用按需调页(Demand Paging),因此当malloc ()返回的时候,虽然是成功返回,但是内核并没有真正给这个进程内存,这个时候如果去读申请的内存,内容全部是0,这个页面的映射是只读的。只有当写到某个页面的时候,内核才在页错误后,真正把这个页面给这个进程。在Linux内核空间中申请内存涉及的函数主要包括kmalloc( ) 、get free pages ( )和vmalloc ()等。kmalloc ()和_get_free pages ()(及其类似函数)申请的内存位于DMA和常规区域的映射区,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmalloc()在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而vmalloc ()申请的虚拟内存和物理内存之间也没有简单的换算关系。
代码如下(建议使用C++编译器编译或支持最新C标准的编译器,我在C11标准下通过):
#include <stdio.h>。
#include <string.h>。
#include <malloc.h>。
void Trim(char *str) {。
char *strTmp = (char *)malloc(sizeof(char) * strlen(str));。
int i = 1, j = 1;。
while (str[j] != '\0') {。
if (str[j] != str[j - 1]) {。
strTmp[i - 1] = str[j - 1];。
i++;
j++;
} else {
j++;
}
}
strTmp[i - 1] = str[j - 1];。
strcpy(str, strTmp);。
free(strTmp);
int main() {
char *str1 = "a3B2c1";。
char *str2 = "Q5BcF570";。
char *str3 = (char *)malloc(sizeof(char) * (strlen(str1) + strlen(str2)));。
strcpy(str3, str1);。
strcat(str3, str2);。
for (int i = 0; i < strlen(str3); i++)。
for (int j = 0; j < i; j++)。
if (str3[i] < str3[j]) {。
char cTmp = str3[i];。
str3[i] = str3[j];。
str3[j] = cTmp;。
}
Trim(str3);
printf("%s + %s => %s\n", str1, str2, str3);。
free(str3);
return 0;
#include<stdio.h>。
#include<stdlib.h>。
#include<ctype.h> //isalpha()函数的头文件。
int main()
char *p = NULL;。
p =(char *)malloc(100*sizeof(char)); //将malloc函数返回的void *指针强制转换为char *指针。
printf("请输入字符串:\n");。
gets(p); //输入字符串。
printf("\n删除了所有空格和标点符号的字符串\n");。
while (*p)
if (isalpha(*p))。
printf("%c", *p);。
p++;
printf("\n");
return 0;
运行效果:
扩展资料:
1、isalpha()函数
作用:判断是否为字母
头文件:#include<ctype.h>。
原型:int isalpha(int ch)。
返回值:若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0。
2、gets()函数
原型:gets(数组名)
作用:把输入的字符串传入给定的数组中。
头文件:#include<stdio.h>。
返回值:正常时返回字符串存放的数组的首地址(指针),错误或遇到EOF时返回NULL。
3、while(*p)
解读:*p内容有值,也就是while(*p)等同于while(*p!='\0'),\0是字符串结束的标志,字符串结束之前都有值。
4、printf("%c",*p)。
等同于putchar(*p),putchar()函数作用是向终端输出一个字符。
5、scanf()函数与gets()函数的区别。
在于输入的字符串是否中间有空格,对于gets()函数,只有遇到'\n'时才停止输入,对于scanf()函数,出现'\n'或空格都停止输入。