C语言函数内通过结构体指针赋值 返回main时填充内容丢失
问题根因
返回main函数后结构体内容丢失,核心是几个C语言值传递和内存分配的典型错误:
- C语言函数参数是值传递,你在
carica函数内部给形参txt做malloc赋值,只会修改函数栈上的txt副本,main里定义的testo* txt始终是未初始化的野指针,根本拿不到函数内分配的结构体地址。 txt->f的内存分配逻辑错误:f是char**类型,本质是存储n个字符串指针的数组,不需要直接分配n*BUFSIZ长度的字符空间,当前写法会造成后续指针访问越界。- 字符串内存分配没有预留
'\0'结束符的位置:strlen返回的长度不包含字符串结束符,malloc(strlen(aux))会少分配1字节,strcpy执行时会出现堆越界写入。 - 清空
aux数组的循环逻辑是未定义行为:第一次进入循环时aux是栈上未初始化的内存,判断aux[j]!='\0'的结果不可预期,实际上fgets会自动在读取内容末尾加结束符,不需要手动逐字节清空数组。
修正方案
如果要保留「在函数内部完成结构体内存分配」的逻辑,最直接的改法是把结构体参数改成二级指针,传入main中txt指针的地址,让函数可以修改外部指针的指向。
修正后的可运行代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct{ char** f; int n; }testo; int carica(char*, testo**); void mostra(testo*); int main(int argc, char *argv[]) { testo* txt; int ret = carica("prova.txt", &txt); if(ret != 0){ printf("加载文件失败,错误码:%d\n", ret); return 1; } mostra(txt); // 用完手动释放内存,避免泄漏 for(int i=0; i<txt->n; i++){ free(txt->f[i]); } free(txt->f); free(txt); return 0; } int carica(char* nf, testo** txt){ FILE* file; if( (file = fopen(nf,"r") )==NULL){ return -1; } int n_of_strings; if(fscanf(file,"%d",&n_of_strings)==EOF || n_of_strings<1){ fclose(file); return -2; } // 读走fscanf读取数字后残留的换行符 getc(file); *txt = malloc(sizeof(testo)); if (*txt==NULL){ fclose(file); return -3; } (*txt)->n = n_of_strings; // 给指针数组分配空间,每个元素是char*类型 (*txt)->f = malloc(sizeof(char*) * n_of_strings); if((*txt)->f == NULL){ free(*txt); fclose(file); return -3; } char aux[BUFSIZ]; for(int i=0; i<n_of_strings; i++){ if(fgets(aux, BUFSIZ, file) == NULL){ // 处理文件行数不足的异常 for(int j=0; j<i; j++) free((*txt)->f[j]); free((*txt)->f); free(*txt); fclose(file); return -2; } // 去掉fgets读入的行尾换行符 size_t len = strlen(aux); if(len>0 && aux[len-1] == '\n'){ aux[len-1] = '\0'; len--; } // 多分配1字节存字符串结束符 (*txt)->f[i] = malloc(len + 1); if ((*txt)->f[i]==NULL){ for(int j=0; j<i; j++) free((*txt)->f[j]); free((*txt)->f); free(*txt); fclose(file); return -4; } strcpy((*txt)->f[i], aux); } fclose(file); return 0; } // 测试用打印函数 void mostra(testo* t){ for(int i=0; i<t->n; i++){ printf("%d: %s\n", i, t->f[i]); } }
额外注意事项
- 所有错误分支都要提前关闭文件、释放已分配的内存,避免出现内存泄漏、文件句柄泄漏。
- 如果不想修改函数签名,也可以在main函数中提前定义栈上的结构体变量、或者提前给
testo* txt分配好结构体内存,再把指针传入carica函数直接填充成员即可,不需要在函数内给结构体本身分配空间。
内容的提问来源于stack exchange,提问作者Davide Marchesi
相关产品推荐
相关产品推荐

