C++封装内存管理函数malloc和free

全文以SSD6 Exercise3为例。

按照题意,加上额外的信息,在malloc里类似这样做。

void *MyMalloc(size_t size, char *filename, int linenumber) {
	void* p = malloc(strlen(filename) + sizeof(linenumber) + sizeof(fence) + size + sizeof(fence));
	void* res = (void*)((int*)((char*)p + sizeof(filename)) + sizeof(linenumber) + sizeof(fence));

	return res;
}

但是这样做的话,在free里很难知道有效载荷(PayLoad)是多长,于是查找资料。

malloc和free函数详解 - Healtheon - 博客园 (cnblogs.com)

这篇文章给了我灵感,可以使用结构体来封装有效载荷和需要添加的额外的数据。

这里没法把filename放到header里,因为我们要存两个指针,一个是header一个是block,block是要通过hader+sizeof header计算的,所以header的大小必须是固定的。

typedef struct Header {
	int checkSum;
	int fence;
}HEADER;

typedef struct Footer {
	int fence;
}Footer;

typedef struct Block {
	HEADER* head;
	void* payLoad;
	Footer* foot;

	char* filename;
	int linenumber;
}BLOCK;

下面开始写MyMalloc函数。

思路是首先创建header,size加上footer这三部分,filename先放一放,因为我们不知道它的固定长度。

先不考虑边界对齐,代码长这个样子。

void *MyMalloc(size_t size, char *filename, int linenumber) {
	void* block_begin = NULL;
	if (!(block_begin = malloc(sizeof(Header) + size + sizeof(Footer)))) {
		return NULL;
	}
	//int cnt = malloc_usable_size(block_begin);
	//printf("%d", cnt);
	void* ptr = (Header*)block_begin + 1;
	Footer* footer = (char*)ptr + AlignBorder(size);

	Header head;
	head.fence = FENCE;
	head.size = size;
	head.checkSum = 1;
	memcpy(block_begin, &head, sizeof(Header));

	Footer foot;
	foot.fence = FENCE;
	memcpy(footer, &foot, sizeof(Footer));

	// Create Block
	Blocker block;

	return ptr;
}

在做MyFree的时候,发现需要拿到MyMalloc的文件名和行号,看来还是需要存一个Block列表的。

Block列表大体需要三个方法,addToList,removeFromList和findInList。

首先,为了语义通顺,这三个函数的参数使用的都是PayLoad的指针而不是head或block的指针。

addToList主要做四件事:

  1. 分配block的内存。
  2. 分配filename的内存。
  3. 初始化block。
  4. 将block插入列表中。
nt addToList(void* ptr, char* filename, int linenumber) {
	Header* header = (Header*)ptr - 1;
	Footer* footer = (Footer*)((char*)ptr + header->size);

	// Allocate memory for block
	Block* block = malloc(BLOCKSIZE);
	if (!block) {
		return -1;
	}

	// Allocate memory for filename
	size_t len = strlen(filename) + 1;
	char* name = (char*)malloc(len);
	if (!name) {
		return -1;
	}
	strcpy_s(name, strlen(filename) + 1, filename);

	// Initailize block
	block->head = header;
	block->payLoad = ptr;
	block->foot = footer;
	block->filename = name;
	block->linenumber = linenumber;

	// Head plug
	block->next = block_head;
	block_head =  block;

	return 0;
}

removeFromList做的三件事:

  1. 遍历链表并将目标结点删除。
  2. 释放目标节点申请的filename的内存。
  3. 释放目标节点申请的block的内存。
// Remove Block
int removeFromList(void* ptr) {
	Block* head = block_head;
	Block* pre = NULL;

	// traverse 
	while (head) {
		if (head->payLoad == ptr) {
			if (pre) {
				pre->next = head->next;
			}
			else { // situation of no pre node
				block_head = block_head->next;
			}

			// free
			free(head->filename);
			head->filename = NULL;
			free(head);
			head = NULL;

			return 0;
		}

		pre = head;
		head = head->next;
	}

	return -1;
}

最后是findInList,这个就很简单了,遍历链表找到目标节点并返回即可。

// Find Block
Block* findInList(void* ptr) {
	Block* b = block_head;
	while (b){
		if (b->payLoad == ptr){
			return b;
		}
		b = b->next;
	}
	return NULL;
}

将上面三个函数写好之后,继续在MyMalloc里面做修改,将ptr指向的空间申请好之后将必要的几个信息整合为Block添加到链表中,整合的代码在addToList里做了,这里调用一下即可。

	// Create Block
	if (addToList((void*)ptr, filename, linenumber) == 0)
	{
		//allocatedSize += h.size;
		return (void*)(ptr);
	}
	else
	{
		free(block_begin);
		return NULL;
	}

最后是MyFree函数,这里我们释放之前需要先判断一下错误,于是封装了一个检查错误的函数。

根据题意:

  • 当向前越界(头部的fence被破坏)时,返回1.
  • 当向后越界(尾部的fence被破坏)时,返回2
  • 当checkSum被修改时,返回3
  • 没有任何问题,返回0
int check_block(void* ptr) {
	Header* header = (Header*)ptr - 1;
	Footer* footer = (Footer*)((char*)ptr + header->size);

	if (header->fence != FENCE) {
		return 1;
	}else if (footer->fence != FENCE) {
		return 2;
	}else if (header->checkSum != 1){
		return 3;
	}

	return 0;
}

第四项错误是,double free,这个错误可以在MyFree里直接判断。

于是MyFree长这个样子。

void MyFree(void* ptr, char* filename, int linenumber) {
	Header* head = (Header*)ptr - 1;
	Block* block = findInList(ptr);
	if (!block) {
		error(4, filename, linenumber);
	}

	int num = check_block(ptr);
	if (num) errorfl(num, block->filename, block->linenumber, filename, linenumber);

	allocatedSize -= head->size;
	free(head);
	head = NULL;
	removeFromList(ptr);
}

大体上就是这些内容了,后面还有两个简单的小函数,打印列表和检测剩余堆内存的函数。

/* Prints a list of all allocated blocks with the
	filename/line number when they were MALLOC'd */
void PrintAllocatedBlocks() {
	Block* head = block_head;
	while (head) {
		printf("filename: %s, linenumber: %d", head->filename, head->linenumber);
		head = head->next;
	}
	return;
}

/* Goes through the currently allocated blocks and checks
	to see if they are all valid.
	Returns -1 if it receives an error, 0 if all blocks are
	okay.
*/
int HeapCheck() {
	Block* head = block_head;
	while (head) {
		int num = check_block(head->payLoad);
		if (num) {
			PRINTERROR(num, head->filename, head->linenumber);
			return -1;
		}

		head = head->next;
	}
	return 0;
}

总结

内存错误(memory bugs)大体上分为五种:

  1. 前向越界
  2. 后向越界
  3. 校验和错误
  4. double free
  5. Memory leak

前三个都很好检测,重点是后面两个,doule free是通过遍历链表查看堆链表中没有目标指针,说明已经被释放过或者压根儿就没创建过,这里要进行报错。接着,内存泄漏是通过allocatedSize变量记录的值来判断的,如果大于零说明有未被释放的内存,然后接着遍历列表打印,从而可以知道未被释放的内存是哪里创建的。

处理上述问题所用到的结构体主要是Block,它的主要成员变量指针malloc时的关系图如下图所示。

 

ps:case3有边界对齐的问题,32位系统应该是4个字节取一次地址,但是没有写这部分代码并不影响结果,查看内存发现malloc申请时并未遵循边界对齐的原则,可能是不同编译器的不同策略。

	case 3: { /* should overflow by 1, harder to catch
				because of alignment */
		char* str = (char*)MALLOC(2);
		strcpy_s(str, strlen(str), "12");
		FREE(str);
	}
int AlignBorder(size_t size) {
	if ((size & 0x3) == 0) {
		return size;
	}
	else {
		return (size >> 2 + 1) << 2;
	}
}

​​​​​​​(6条消息) 位运算取余_actionzh的博客-CSDN博客_位运算取余

使用位运算取余提高效率,如果不是4的倍数,则右移加一再左移得到的是比size大的最小的4的倍数的数。

如下图,两字节申请是紧紧挨着Header和Footer的。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值