如何为结构体中不定大小数组正确使用malloc分配内存
定义的结构体
struct KMData{ int ndata; int dim; float **features; int *assigns; int *labels; int nlabels; };
问题描述
编写kmdata_load函数时,尝试根据输入文件大小为结构体的features和labels分配内存,但打印features的大小仅为8,远小于预期。调整结构体内存分配方式为:
struct KMData *data = malloc(sizeof(struct KMData)); data->labels = malloc(lines * sizeof(int));
后,执行data->labels[data->ndata-1] = atoi(token);触发段错误,需排查内存分配中的问题。
完整函数代码
struct KMData kmdata_load(char *datafile) { ssize_t tokens; ssize_t lines; filestats(datafile, &tokens, &lines); struct KMData *data = malloc(sizeof(struct KMData) + (lines * sizeof(int)) + ((tokens - (2*lines)) * sizeof(float))); printf("tokens: %zd, lines: %zd\n", tokens, lines); data->labels = malloc(lines * sizeof(int)); data->features = malloc((tokens - (2*lines)) * sizeof(float)); ssize_t f_size = sizeof(*(data->features)); printf("size of features: %zd\n", f_size); FILE *fin = fopen(datafile, "r"); char line[3150]; int i = 0; while (fgets(line, 3150, fin)) { data->ndata++; char *token = strtok(line, " \t"); data->labels[data->ndata-1] = atoi(token); float feats[(tokens/lines)-2]; int f = 0; token = strtok(NULL, " \t"); while ((token = strtok(NULL, " \t"))) { feats[f] = atof(token); data->features[data->ndata-1][f] = atof(token); if(i==0){ printf("token %d: %f\n", f, data->features[data->ndata-1][f]); } f++; } i++; ssize_t size = sizeof(feats)/sizeof(float); } fclose(fin); return *data; } int main(int argc, char* argv[]){ struct KMData data = kmdata_load(argv[1]); }
1. features 类型匹配错误
features是float**类型(指向float指针的指针),但当前用malloc((tokens - (2*lines)) * sizeof(float))分配的是连续float数组,仅对应float*类型,完全不符合float**的结构要求。
修复方式:
先分配样本数量对应的指针数组,再为每个指针分配单样本的特征内存:
// 计算单样本特征维度 int dim = (tokens - lines) / lines; data->dim = dim; // 分配行指针(对应每个样本) data->features = malloc(lines * sizeof(float*)); // 为每个样本分配特征内存 for (int i = 0; i < lines; i++) { data->features[i] = malloc(dim * sizeof(float)); }
另外,你打印的sizeof(*(data->features))得到8,是因为64位系统中float*指针本身占8字节,这个值和你分配的数组内存大小无关,该打印没有意义。
2. ndata 未初始化导致越界
结构体中的ndata未初始化,初始值是随机垃圾值。第一次执行data->ndata++后,data->ndata-1可能是一个极大的数,直接访问data->labels[data->ndata-1]会触发数组越界,导致段错误。
修复方式:
分配结构体后立即初始化ndata为0:
struct KMData *data = malloc(sizeof(struct KMData)); data->ndata = 0; // 初始化至关重要 data->labels = malloc(lines * sizeof(int));
3. 冗余的内存分配操作
一开始尝试将结构体、labels、features的内存一次性分配:
struct KMData *data = malloc(sizeof(struct KMData) + (lines * sizeof(int)) + ((tokens - (2*lines)) * sizeof(float)));
但随后又单独为labels和features调用malloc,导致之前分配的额外内存被浪费,逻辑混乱。建议分开分配结构体和内部数组,避免混合操作。
4. strtok 逻辑错误
读取特征时,先调用token = strtok(NULL, " \t");,进入循环又调用一次token = strtok(NULL, " \t");,会跳过第一个特征值。
修复方式:
调整strtok调用逻辑,确保读取所有特征:
token = strtok(line, " \t"); data->labels[data->ndata-1] = atoi(token); int f = 0; // 直接读取第一个特征 token = strtok(NULL, " \t"); while (token != NULL) { data->features[data->ndata-1][f] = atof(token); if (i == 0) { printf("token %d: %f\n", f, data->features[data->ndata-1][f]); } f++; token = strtok(NULL, " \t"); }
5. 内存泄漏风险
当前函数返回结构体副本(return *data;),但原结构体指针data未被释放,会导致内存泄漏。建议修改函数返回struct KMData*指针,使用后手动释放内存:
// 修改函数返回类型 struct KMData* kmdata_load(char *datafile) { // ... 原有逻辑 ... return data; } // main函数中使用 struct KMData *data = kmdata_load(argv[1]); // 使用完毕后释放内存 for (int i = 0; i < data->ndata; i++) { free(data->features[i]); } free(data->features); free(data->labels); free(data);
内容的提问来源于stack exchange,提问作者Daisuke Oto

