C语言解决free invalid pointer 导致的core dumped问题引发的思考

C语言解决free invalid pointer 导致的core dumped问题引发的思考

背景

在公司的项目中,编译运行后出来free invalid pointer的问题,导致程序运行直接段错误。

出现问题的代码片段

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
	char * test_data = (char*)malloc(24);
	if (test_data == NULL) {
		printf("test data malloc failed\n");
		return 0;
	}
	
	bzero(test_data, 24);
	
	char test[] = "HelloT1mzhouCSDNTEST";
	strcpy(test_data, test);
	
	char buf[24];
	for (int i = 0; i < 24; i++) {
		buf[i] = *test_data++;
	}
	
	free(test_data);
	test_data = NULL;

	return 0;
}

解决方案

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
	char* test_data = (char*)malloc(24);
	if (test_data == NULL) {
		printf("test data malloc failed\n");
		return 0;
	}
	
	bzero(test_data, 24);
	
	char test[] = "HelloT1mzhouCSDNTEST";
	strcpy(test_data, test);
	
	char buf[24];
	char* tmp =  test_data;	// 使用临时指针指向malloc的空间
	for (int i = 0; i < 24; i++) {
		buf[i] = *tmp++;	// 数据操作使用tmp指针
	}
	
	free(test_data);
	test_data = NULL;

	return 0;
}

为什么需要这样处理?

glibc-2.2.3源码

/* Declaration changed to standard one for GNU.  */
void *
malloc(size)
	register size_t size;
{
	register int i, n;
	register free_list_t fl;
	register header_t h;

	if ((int) size < 0)		/* sanity check */
		return 0;
	size += HEADER_SIZE;
	/*
	 * Find smallest power-of-two block size
	 * big enough to hold requested size plus header.
	 */
	i = 0;
	n = MIN_SIZE;
	while (n < size) {
		i += 1;
		n <<= 1;
	}
	ASSERT(i < NBUCKETS);
	fl = &malloc_free_list[i];
	spin_lock(&fl->lock);
	h = fl->head;
	if (h == 0) {
		/*
		 * Free list is empty;
		 * allocate more blocks.
		 */
		more_memory(n, fl);
		h = fl->head;
		if (h == 0) {
			/*
			 * Allocation failed.
			 */
			spin_unlock(&fl->lock);
			return 0;
		}
	}
	/*
	 * Pop block from free list.
	 */
	fl->head = HEADER_NEXT (h);

#ifdef MCHECK
	assert (HEADER_CHECK (h) == CHECK_FREE);
	HEADER_CHECK (h) = CHECK_BUSY;
#endif

#ifdef	DEBUG
	fl->in_use += 1;
#endif	/* DEBUG */
	spin_unlock(&fl->lock);
	/*
	 * Store free list pointer in block header
	 * so we can figure out where it goes
	 * at free() time.
	 */
	HEADER_FREE (h) = fl;
	/*
	 * Return pointer past the block header.
	 */
	return ((char *) h) + HEADER_SIZE;
}

/* Declaration changed to standard one for GNU.  */
void
free(base)
	void *base;
{
	register header_t h;
	register free_list_t fl;
	register int i;

	if (base == 0)
		return;
	/*
	 * Find free list for block.
	 */
	h = (header_t) (base - HEADER_SIZE);

#ifdef MCHECK
	assert (HEADER_CHECK (h) == CHECK_BUSY);
#endif

	fl = HEADER_FREE (h);
	i = fl - malloc_free_list;
	/*
	 * Sanity checks.
	 */
	if (i < 0 || i >= NBUCKETS) {
		ASSERT(0 <= i && i < NBUCKETS);
		return;
	}
	if (fl != &malloc_free_list[i]) {
		ASSERT(fl == &malloc_free_list[i]);
		return;
	}
	/*
	 * Push block on free list.
	 */
	spin_lock(&fl->lock);
	HEADER_NEXT (h) = fl->head;
#ifdef MCHECK
	HEADER_CHECK (h) = CHECK_FREE;
#endif
	fl->head = h;
#ifdef	DEBUG
	fl->in_use -= 1;
#endif	/* DEBUG */
	spin_unlock(&fl->lock);
	return;
}

我们很显然能看到malloc申请的空间返回的地址偏移了HEADER_SIZE个字节,这意味着实际我们申请出来的空间是大于我们实际所申请的空间大小。

malloc的头部信息记录在header_t这个结构中。我们查看该结构体内容

typedef union header {
	union header *next;
	struct free_list *fl;
} *header_t;


typedef struct free_list {
	spin_lock_t lock;	/* spin lock for mutual exclusion */
	header_t head;		/* head of free list for this size */
#ifdef	DEBUG
	int in_use;		/* # mallocs - # frees */
#endif	/* DEBUG */
} *free_list_t;

我们可以看出头部记录了内存的使用情况。

接着我们看free源码

我们转入需要释放的空间时,会向前偏移HEADER_SIZE个字节才能正确找到需要释放空间的地址;这也是我们为什么需要用tmp变量去做实际的数据操作。这种操作时为了保证free传入的指针地址没有偏移。释放时,free能根据该地址偏移进而正确释放申请空间。

扩展话题

C++中new一个数组,使用delete释放会怎么样?

  • 以VC为例子

VC new之后的内存分布

图示说明:
  • 第一个内存分布图,是VC debug模式下,申请3个String对象数组后动态分配的内存情况;

  • 第二个内存分布图,是VC release版本下,申请3个String对象数组后动态分配的内存情况;

图一解释:41h为动态内存申请后vc标记动态申请内存的大小,41h为64的16进制数据+1,1为标识内存被使用。橘黄色的部分为Debug需要的信息,Debugger headerdebug的头部信息。3为申请的数组大小。pad为填充数据,最后大小为64是因为内存对齐。

图二解释:21h为动态内存申请后vc标记动态申请内存的大小,21h为32的16进制数据+1,1为标识内存被使用。3为申请的数组大小。最后大小为32是因为内存对齐。


vc下new string[3]
由左图可以看出:动态申请String数组后,使用delete[],能正确调用析构函数对内存进行释放;

由右图可以看出:动态申请String数组后,如何使用delete区释放内存,只会调用一次析构函数,剩下的2块动态申请的空间内存会泄漏;

  • C++中new一个空间,使用delete []释放会怎么样?

参考资料

[glibc-2.2.3源码](Index of /gnu/glibc)

侯捷CPP-面向对象高级编程课件

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值