背景
最近刚刚转到C技术栈,直接就碰到了C语言的常见问题:内存越界。
检查内存越界的工具有很多,这里笔者采用mcheck,因为这是glibc原生的工具,不需要额外下载。但是,深入使用后发现mcheck有个坑:
- 功能过于简单,mcheck原生方法只会在遇到内存问题时上报,具体是哪个指针被踩了,哪个指针没有回收,哪个指针double free了没有提示出来,不利于问题定位。
为了方便实用,花几分钟写了一个新的结构体+单链表封装mcheck,以增强其检查函数。实现比较简单,仅供学习使用。
源码
#include <stdio.h>
#include <stdlib.h>
#include <mcheck.h>
#include <string.h>
#define VARIABLE_NAME(var) #var
class TestStruct {
public:
int id;
};
struct SmartPtr {
void* ptr;
const char* ptr_name;
SmartPtr* next = NULL;
public:
SmartPtr(void* ptr, const char* ptr_name) {
this->ptr = ptr;
this->ptr_name = ptr_name;
}
static void create_block(void* ptr, const char* ptr_name);
static void checkAllPtr();
};
static SmartPtr* root = NULL;
void SmartPtr::create_block(void* ptr, const char* ptr_name) {
if (ptr == NULL) {
printf("[var_name = %s]Failed to create smart ptr: ptr is NULL.\n");
return;
}
SmartPtr* new_block = new SmartPtr(ptr, ptr_name);
if (root == NULL) {
root = new_block;
} else {
SmartPtr* tail = root;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = new_block;
}
};
void SmartPtr::checkAllPtr() {
// 遍历指针
SmartPtr* tmp = root;
while(tmp != NULL) {
printf("[var_name = %s]check ptr.\n", tmp->ptr_name);
mprobe(tmp->ptr);
tmp = tmp->next;
}
}
void print_mstatus(enum mcheck_status mstatus){
switch(mstatus){
case MCHECK_DISABLED:
printf("consistency checking is not turned on\n");
break;
case MCHECK_OK:
printf("block is fine\n");
break;
case MCHECK_FREE:
printf("block freed twice\n");
break;
case MCHECK_HEAD:
printf("memory before the block was clobbered\n");
break;
case MCHECK_TAIL:
printf("memory after the block was clobbered\n");
break;
default:
printf("Memory is OK.\n");
break;
}
}
void abortfun(enum mcheck_status mstatus) {
fprintf(stderr, "abortfun called with %d\n", mstatus);
print_mstatus(mstatus);
}
int main(int argc, char *argv[]) {
TestStruct *p1, *p2, *tmpp = NULL;
enum mcheck_status mstatus;
int result = mcheck(abortfun);
if( result != 0) {
fprintf(stderr, "mcheck:%d\n", result);
return -1;
}
p1 = (TestStruct*)malloc(sizeof(TestStruct) * 10);
p2 = (TestStruct*)malloc(sizeof(TestStruct) * 10);
SmartPtr::create_block(p1, VARIABLE_NAME(p1));
SmartPtr::create_block(p2,VARIABLE_NAME(p2));
SmartPtr::create_block(tmpp,VARIABLE_NAME(tmpp));
tmpp = p1 + 10;
tmpp[0].id = 10; // 内存块后越界
tmpp = p2 - 1;
tmpp[0].id = 10; // 内存块前越界
// 检测内存是否越界
printf("checkAllPtr starts...\n");
SmartPtr::checkAllPtr();
printf("checkAllPtr ends...\n");
// mcheck 原生方法,检查所有堆内的内存
// mcheck_check_all();
// printf("mcheck_check_all finished.\n");
free(p1);
free(p2);
return 0;
}
样例输出
以上实例的输出为:
[var_name = tmpp]Failed to create smart ptr: ptr is NULL.
checkAllPtr starts...
[var_name = p1]check ptr.
abortfun called with 3
memory after the block was clobbered
[var_name = p2]check ptr.
abortfun called with 2
memory before the block was clobbered
checkAllPtr ends...
abortfun called with 3
memory after the block was clobbered
abortfun called with 2
memory before the block was clobbered
总结
由于mcheck的报告信息过于简单,不利于问题定位,笔者扩展了mcheck,使其能保持内存错误的具体位置。 但是笔者不建议直接使用mcheck,原因如下:
- 相关资料不多,深入使用时,经常要自己去看代码。
- mcheck经常莫名奇妙就被disable了,显式调用mcheck()、启动参数加上-lmcheck也没用,当然这点可能是笔者功底不足。
建议使用valgrind,这是一个封装得更好的工具,功能更强大,相关资料、文档也更丰富。