You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

返回包含字符指针数组的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 16:21:11