c++内存泄露,常由于程序中未释放不再使用的动态分配的内存导致。因此,真正理解所需要的分配的内存对象的范围大小是很有必要的。更重要的是,要明白何时调用 free。但当程序复杂度增加时,要确定 free 的调用时机将变得更加困难。早期设计决策时,规划内存很重要。下面西安达内c++培训讲师就详细为大家介绍。
以下是处理c++内存泄露的技能表:
1) 启动时分配
想让内存管理保持简单,一个方法是在启动时在堆中分配所有所需的内存。程序结束时,释放内存的重任就交给了操作系统。这种方法在许多场景中的效果令人满意,特别是当程序在一个批量操作中完成对输入的处理的情况。
2) 变长数组
如果你需要有着变长大小的临时存储,并且其生命周期在变量内部时,可考虑VLA(Variable Length Array,变长数组)。但这有个限制:每个函数的空间不能超过数百字节。因为C99指出边长数组能自动存储,它们像其他自动变量一样受限于同一作用域。即便标准未明确规定,VLA的实现都是把内存数据放到栈中。VLA的最大长度为SIZE_MAX字节。考虑到目标平台的栈大小,我们必须更加谨慎小心,以保证程序不会面临栈溢出、下个内存段的数据损坏的尴尬局面。
3) 自己编写引用计数
这个技术的想法是对某个内存对象的每次引用、去引用计数。赋值时,计数器会增加;去引用时,计数器减少。当引用计数变为0时,这意味着此内存对象不再被使用,可以释放。因为C不提供自动析构(事实上,GCC和Clang都支持cleanup语言扩展), 也不是重写赋值运算符,引用计数由调用retain/release的函数手动完成。更好的方式,是把它作为程序的可变部分,能通过这部分获取和释放一个内存对象的拥有权。但是,使用这种方法需要很多(编程)规范来防止忘记调用release(停止内存泄露)或不必要地调用释放函数(这将导致内存释放地过早)。若内存对象的生命期需要外部事件指出,或应用程序的数据结构隐含了某个内存对象的持有权的处理,无论何种情况,都容易导致问题。下述代码块含有简化了的内存管理引用计数。
#include
#include
#define MAX_REF_OBJ 100
#define RC_ERROR -1
struct mem_obj_t{
void *ptr;
uint16_t count;
};
static struct mem_obj_t references[MAX_REF_OBJ];
static uint16_t reference_count = 0;
/* create memory object and return handle */
uint16_t create(size_t size){
if (reference_count >= MAX_REF_OBJ)
return RC_ERROR;
if (size){
void *ptr = calloc(1, size);
if (ptr != NULL){
references[reference_count].ptr = ptr;
references[reference_count].count = 0;
return reference_count++;
}
}
return RC_ERROR;
}
/* get memory object and increment reference counter */
void* retain(uint16_t handle){
if(handle < reference_count && handle >= 0){
references[handle].count++;
return references[handle].ptr;
} else {
return NULL;
}
}
/* decrement reference counter */
void release(uint16_t handle){
printf("release\n");
if(handle < reference_count && handle >= 0){
struct mem_obj_t *object = &references[handle];
if (object->count <= 1){
printf("released\n");
free(object->ptr);
reference_count--;
} else {
printf("decremented\n");
object->count--;
}
}
}
如果你关心编译器的兼容性,可用 cleanup 属性在C中模拟自动析构。
void cleanup_release(void** pmem) {
int i;
for(i = 0; i < reference_count; i++) {
if(references[i].ptr == *pmem)
release(i);
}
}
void usage() {
int16_t ref = create(64);
void *mem = retain(ref);
__attribute__((cleanup(cleanup_release), mem));
/* ... */
}
上述方案的另一缺陷是提供对象地址让 cleanup_release 释放,而非引用计数值。这样一来,cleanup_release 必须在 references 数组中做开销大的查找操作。一种解决办法是,改变填充的接口为返回一个指向 struct mem_obj_t 的指针。另一种办法是使用下面的宏集合,这些宏能够创建保存引用计数值的变量并追加 clean 属性。
/* helper macros */
#define __COMB(X,Y) X##Y
#define COMB(X,Y) __COMB(X,Y)
#define __CLEANUP_RELEASE __attribute__((cleanup(cleanup_release)))
#define retain_auto(REF) retain(REF); int16_t __CLEANUP_RELEASE COMB(__ref,__LINE__) = REF
void cleanup_release(int16_t* phd) {
release(*phd);
}
void usage() {
int16_t ref = create(64);
void *mem = retain_auto(ref);
/* ... */
}
(注:##符号源自C99,用于连接两个变量的名称,一般用在宏里。如int a##b就会定义一个叫做ab的变量;__LINE__指代码行号,类似的还有__FUNCTION__或__func__和__FILE__,可用于打印调试信息;__attribute__符号来自gcc,主要用于指导编译器优化,也提供了一些如构造、析构、字节对齐等功能)
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/29562929/viewspace-1220498/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/29562929/viewspace-1220498/