返回包含字符指针数组的C结构体失败的问题排查与解决
C语言字符串分割函数
strSplit的token访问异常问题 我在基于C语言实现字符串分割函数strSplit时遇到问题:从strSplit()函数返回结构体指针后,token的处理出现异常。变量sd->tokens地址正确,但无法获取到token内容;不过在函数内部却能正常访问。结构体中其余变量均正常,请问该问题的原因是什么?如何解决?
原始代码
#include <stdio.h> #include <string.h> struct splitResult { char *source; long lSource; int result; char **tokens; }; typedef struct splitResult splitResultStruct; splitResultStruct * strSplit(char *source, char *delimiter); int main(int argc, const char * argv[]) { char *source = "part1.part2.part3"; splitResultStruct *sd; sd = strSplit(source, "."); printf("%d tokens found\n", sd->result); for(int i=0; i<sd->result; i++) { printf("%s\n", sd->tokens[i]); } return 0; } splitResultStruct * strSplit(char *source, char *delimiter) { // Defines the result struct splitResultStruct sData, *sDataPtr; sDataPtr = &sData; sData.source = source; // Gets the length of source string sData.lSource = strlen(source); // Don't split if empty string is given if(sData.lSource == 0) { sData.result = -1; return sDataPtr; } // Allocate memory according teh size of source string char data[sData.lSource]; // Copy the source into the allocated memory strcpy(data,source); // Just count the tokens char *token = strtok(data, delimiter); int tc = 0; while (token != NULL) { token = strtok(NULL, delimiter); tc++; } if(tc == 0) { sData.result = -1; return sDataPtr; } // Defines an array of char pointer with the dimension of the number of tokens sData.result = tc; char *tokens[tc]; // Resets the token engine strcpy(data,source); // Strip out the first token found token = strtok(data, delimiter); tokens[0] = token; tc = 0; while (token != NULL) { // Strip out one token and store them into the token array token = strtok(NULL, delimiter); tc++; tokens[tc] = token; } sData.tokens = tokens; for(int i=0; i<sData.result; i++) { printf("%s\n", sData.tokens[i]); } return sDataPtr; }
问题原因
代码存在两个核心的栈内存生命周期问题:
- 栈结构体指针失效:函数内的
sData是局部栈变量,函数执行完毕返回时,栈帧被销毁,sData的内存会被系统回收或覆盖。返回的sDataPtr指向的是已失效的内存区域,后续访问属于未定义行为——仅普通变量(如result)可能暂时未被覆盖,而tokens相关内存问题更直接。 - 栈数组内存失效:
char data[sData.lSource]是栈上的字符数组,strtok修改的是该栈数组内容,所有token指针都指向这个栈数组地址。char *tokens[tc]也是栈上的指针数组,sData.tokens指向了这个栈数组。函数返回后,这两个栈数组的内存被释放,主函数访问sd->tokens[i]时指向的是无效内存,无法读取正确内容。
解决方法
需要将所有需在函数外部访问的内存改为堆内存分配,确保生命周期不受函数栈帧影响:
步骤1:为结构体分配堆内存
不再使用栈上的结构体,改用malloc分配:
splitResultStruct *sDataPtr = malloc(sizeof(splitResultStruct)); if (sDataPtr == NULL) return NULL;
步骤2:为源字符串副本分配堆内存
替换栈上的data数组,用堆内存存储源字符串副本:
sDataPtr->dataBuf = malloc(sDataPtr->lSource + 1); // +1存储字符串结束符 if (sDataPtr->dataBuf == NULL) { free(sDataPtr); return NULL; } strcpy(sDataPtr->dataBuf, source);
步骤3:为tokens指针数组分配堆内存
替换栈上的tokens数组,用堆内存分配指针数组:
sDataPtr->tokens = malloc(sizeof(char*) * sDataPtr->result); if (sDataPtr->tokens == NULL) { free(sDataPtr->dataBuf); free(sDataPtr); return NULL; }
步骤4:修正token收集逻辑
原循环存在索引越界错误,调整为正确的收集逻辑:
strcpy(sDataPtr->dataBuf, source); char *token = strtok(sDataPtr->dataBuf, delimiter); int idx = 0; while (token != NULL) { sDataPtr->tokens[idx++] = token; token = strtok(NULL, delimiter); }
步骤5:添加内存释放逻辑
堆内存需手动释放避免泄漏,使用完结构体后依次释放:
free(sd->dataBuf); free(sd->tokens); free(sd);
完整修正后的代码
#include <stdio.h> #include <string.h> #include <stdlib.h> struct splitResult { char *source; long lSource; int result; char **tokens; char *dataBuf; // 保存堆上的源字符串副本,用于后续释放 }; typedef struct splitResult splitResultStruct; splitResultStruct * strSplit(char *source, char *delimiter); int main(int argc, const char * argv[]) { char *source = "part1.part2.part3"; splitResultStruct *sd; sd = strSplit(source, "."); if (sd == NULL || sd->result == -1) { printf("分割失败或未找到token\n"); return 1; } printf("找到%d个token\n", sd->result); for(int i=0; i<sd->result; i++) { printf("%s\n", sd->tokens[i]); } // 释放堆内存 free(sd->dataBuf); free(sd->tokens); free(sd); return 0; } splitResultStruct * strSplit(char *source, char *delimiter) { // 为结构体分配堆内存 splitResultStruct *sDataPtr = malloc(sizeof(splitResultStruct)); if (sDataPtr == NULL) { return NULL; } sDataPtr->source = source; sDataPtr->lSource = strlen(source); // 空字符串处理 if(sDataPtr->lSource == 0) { sDataPtr->result = -1; return sDataPtr; } // 为源字符串副本分配堆内存 sDataPtr->dataBuf = malloc(sDataPtr->lSource + 1); if (sDataPtr->dataBuf == NULL) { free(sDataPtr); return NULL; } strcpy(sDataPtr->dataBuf, source); // 统计token数量 char *token = strtok(sDataPtr->dataBuf, delimiter); int tc = 0; while (token != NULL) { tc++; token = strtok(NULL, delimiter); } if(tc == 0) { sDataPtr->result = -1; free(sDataPtr->dataBuf); return sDataPtr; } // 为tokens指针数组分配堆内存 sDataPtr->result = tc; sDataPtr->tokens = malloc(sizeof(char*) * tc); if (sDataPtr->tokens == NULL) { free(sDataPtr->dataBuf); free(sDataPtr); return NULL; } // 重新分割并收集token strcpy(sDataPtr->dataBuf, source); token = strtok(sDataPtr->dataBuf, delimiter); int idx = 0; while (token != NULL) { sDataPtr->tokens[idx++] = token; token = strtok(NULL, delimiter); } return sDataPtr; }
内容的提问来源于stack exchange,提问作者Frame Worker
相关产品推荐
相关产品推荐

