分析存在内存泄漏风险的C语言结构体字符串拷贝代码
先把原代码摆出来方便咱们逐个排查:
#include<stdio.h> #include<stdlib.h> #include<malloc.h> #include<string.h> typedef struct nNameStruct{ char* nName; //char* nName2; } nNameStruct; nNameStruct * NameCreate(char *buffer) { nNameStruct *pNew; pNew = (nNameStruct*) malloc(sizeof(nNameStruct*)); pNew->nName = (char*) malloc((strlen(buffer)+1)*sizeof(char*)); strcpy(pNew->nName,buffer); //pNew->nNam... }
1. 结构体内存分配的致命错误
先看给结构体分配内存的那行:
pNew = (nNameStruct*) malloc(sizeof(nNameStruct*));
这是C新手常犯的经典错误——你给malloc传的是结构体指针的大小(sizeof(nNameStruct*),32位平台是4字节,64位是8字节),但咱们需要的是整个结构体的大小(sizeof(nNameStruct))。
可能你会说,当前结构体里只有一个char*成员,sizeof(nNameStruct)刚好等于sizeof(nNameStruct*),好像没毛病?但这纯粹是巧合!一旦你把注释里的nName2放开,或者给结构体加其他成员,这个错误会立刻导致内存分配不足,触发堆越界访问,轻则程序崩溃,重则破坏堆结构,出现各种莫名其妙的bug,排查起来头都大。而且从代码规范来说,这绝对是错误的写法,必须改掉。
2. 字符串内存的浪费型错误
再看给nName分配内存的代码:
pNew->nName = (char*) malloc((strlen(buffer)+1)*sizeof(char*));
这里的问题是用sizeof(char*)来计算单个字符的内存大小,但实际上我们需要的是sizeof(char)——而根据C标准,sizeof(char)固定为1,所以甚至可以直接写strlen(buffer)+1就行。
char*的大小是4或8字节,这意味着你给每个字符分配了4/8倍的内存,完全是不必要的浪费。比如buffer是10个字符,本来只需要11字节(加终止符),现在却分配了44或88字节,要是处理大量长字符串,内存开销会被放大好几倍,太不划算。
3. 潜在的内存泄漏风险
这段代码没有配套的释放函数,要是调用者在使用完nNameStruct后,忘记先释放pNew->nName再释放pNew,就会导致内存泄漏。正确的释放逻辑应该封装成一个函数,比如:
void NameDestroy(nNameStruct *p) { if (p != NULL) { free(p->nName); free(p); } }
每次调用NameCreate创建对象后,用完必须调用这个函数释放,不然内存会一直被占着,直到程序退出。
4. 容易被忽略的边界检查问题
还有两个容易被漏掉的检查:
- 检查
malloc返回值:如果内存不足,malloc会返回NULL,直接用pNew或pNew->nName会触发空指针崩溃。必须添加检查,而且如果nName分配失败,要先释放已经分配的结构体,避免泄漏。 - 检查输入
buffer是否为NULL:如果传入NULL,strlen(buffer)会触发未定义行为,程序直接崩掉,开头必须先判断。
修正后的代码示例
把上面的问题都修复后,代码应该是这样的:
#include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct nNameStruct{ char* nName; //char* nName2; } nNameStruct; nNameStruct * NameCreate(char *buffer) { // 先检查输入合法性 if (buffer == NULL) { return NULL; } // 分配结构体内存,检查是否成功 nNameStruct *pNew = malloc(sizeof(nNameStruct)); if (pNew == NULL) { return NULL; } // 分配字符串内存,检查是否成功 size_t str_len = strlen(buffer); pNew->nName = malloc(str_len + 1); // sizeof(char)=1,可省略 if (pNew->nName == NULL) { free(pNew); // 回滚已分配的内存 return NULL; } // 拷贝字符串 strcpy(pNew->nName, buffer); return pNew; } // 配套的释放函数 void NameDestroy(nNameStruct *p) { if (p != NULL) { free(p->nName); free(p); } }
内容的提问来源于stack exchange,提问作者Enialis

