C语言实现MNIST数据集BP识别加载输入层段错误排查
问题描述
基于C语言使用反向传播算法实现MNIST数据集识别功能时,程序在加载输入层神经单元阶段触发Segmentation fault (core dumped)错误。gdb调试仅返回Program terminated with signal SIGSEGV, Segmentation fault提示,开启ulimit配置后未生成core文件,问题代码片段如下:
#include <stdio.h> #include <unistd.h> #include <math.h> #include <stdlib.h> #include <time.h> #define PATH_TRAIN_IMAGES "../../train-images-idx3-ubyte" #define PATH_TRAIN_LABELS "../../train-labels-idx1-ubyte" #define PATH_WEIGHT_DATA2 "../data/data2.weight" #define PATH_WEIGHT_DATA3 "../data/data3.weight" #define PATH_BIAS_DATA2 "../data/data2.bias" #define PATH_BIAS_DATA3 "../data/data3.bias" #define TRAIN_IMAGES_NUMBER 60000 #define PIXEL 784 #define HIDDEN_UNITS_NUMBER 300 #define OUT_UNITS_NUMBER 10 #define TRAIN_TEST 0 struct Unit { // input with weight float z; // bias float b; // output float a; }; float sigmod(float z) { return (1 / (1 + exp(-z))); } struct Unit* create_unit(float uz, float ub, float ua) { struct Unit* unit = (struct Unit*)malloc(sizeof(struct Unit)); unit->z = uz; unit->b = ub; unit->a = ua; return unit; } int load_train_labels(char* path_train_labels, unsigned char* ar_label) { FILE *fp_label; int size_label = 0; fp_label = fopen(path_train_labels, "rb"); fseek(fp_label, 0, SEEK_END); size_label = ftell(fp_label); printf("%s size:%d byte\n", path_train_labels, size_label); rewind(fp_label); // Starting with the 9th byte fseek(fp_label,8,SEEK_SET); unsigned char train_labels_buffer[size_label]; ar_label = (unsigned char*)malloc(sizeof(unsigned char) * size_label - 8); fread(ar_label, 1, size_label - 8, fp_label); fclose(fp_label); return size_label; } int load_train_images(char* path_train_images, unsigned char* ar_img) { FILE *fp_img; int size_img = 0; fp_img = fopen(path_train_images, "rb"); fseek(fp_img, 0, SEEK_END); size_img = ftell(fp_img); printf("%s size:%d byte\n", path_train_images, size_img); rewind(fp_img); // Starting with the 17th byte, each byte stores the value of one pixel in a picture fseek(fp_img, 16, SEEK_SET); ar_img = (unsigned char*)malloc(sizeof(char) * size_img - 16); fread(ar_img, 1, size_img - 16, fp_img); fclose(fp_img); return size_img; } int load_data(char* path_data, unsigned char* ar_data) { FILE *fp_data; int size_data; fp_data = fopen(path_data, "rb"); fseek(fp_data, 0, SEEK_END); size_data = ftell(fp_data); fseek(fp_data, 0, SEEK_SET); ar_data = (unsigned char*)malloc(sizeof(char) * size_data); printf("%s size:%d byte\n", path_data, size_data); return size_data; } int main(int argc, char *argv[]) { printf("Loading train labels file.\n"); unsigned char* ar_label; int size_label; size_label = load_train_labels(PATH_TRAIN_LABELS, ar_label); printf("Loading train images file.\n"); unsigned char* ar_img; int size_img; size_img = load_train_images(PATH_TRAIN_IMAGES, ar_img); printf("Loading random weight file.\n"); unsigned char* ar_weight2; int size_weight2; size_weight2 = load_data(PATH_WEIGHT_DATA2, ar_weight2); unsigned char* ar_weight3; int size_weight3; size_weight3 = load_data(PATH_WEIGHT_DATA3, ar_weight3); printf("Loading random bias file.\n"); unsigned char* ar_bias2; int size_bias2; size_bias2 = load_data(PATH_BIAS_DATA2, ar_bias2); unsigned char* ar_bias3; int size_bias3; size_bias3 = load_data(PATH_BIAS_DATA3, ar_bias3); float uz = 0; float ub = 0; float ua = 0; struct Unit* out_units[OUT_UNITS_NUMBER]; for (int t = 0; t < OUT_UNITS_NUMBER; t++) { out_units[t] = create_unit(uz, ub, ua); } struct Unit* hid_units[HIDDEN_UNITS_NUMBER]; for(int i = 0; i < HIDDEN_UNITS_NUMBER; i++) { hid_units[i] = create_unit(uz, ub, ua); } struct Unit* in_units[PIXEL] = {NULL}; for(int i = 0; i < PIXEL; i++) { in_units[i] = create_unit(uz, ub, ua); } /******************* * load C1 * *******************/ printf("Loading train...\n"); float C[TRAIN_IMAGES_NUMBER]; for(int i = 0; i < PIXEL; i++) { in_units[i]->a = (float)*((ar_img+i*sizeof(char))); //segmentation fault(core dumped) printf("in_unit[%d] = %f\n", i, in_units[i]->a); } for(int i = 0; i < HIDDEN_UNITS_NUMBER; i++) { for(int j = 0; j < PIXEL; j++) { hid_units[i]->z += in_units[j]->a * ((float)*(ar_weight2+((i*PIXEL+j)*sizeof(float)))); } hid_units[i]->z += ((float)*(ar_bias2+(i*sizeof(float)))); hid_units[i]->a = sigmod(hid_units[i]->z); } for(int i = 0; i < OUT_UNITS_NUMBER; i++) { for(int j = 0; j < HIDDEN_UNITS_NUMBER; j++) { out_units[i]->z += hid_units[j]->a * ((float)*(ar_weight3+((i*HIDDEN_UNITS_NUMBER+j)*sizeof(float)))); } out_units[i]->z += ((float)*(ar_bias3 + (i*sizeof(float)))); out_units[i]->a = sigmod(out_units[i]->z); } // free(in_units) free(ar_label); free(ar_img); free(ar_weight2); free(ar_bias2); free(ar_weight3); free(ar_bias3); return 0; }
错误根因
- 指针传参逻辑错误:C语言函数参数为值传递,三个加载函数内部给形参指针
malloc分配内存时,修改的是函数栈上的局部指针副本,函数返回后主函数中的ar_label/ar_img/ar_weight2等指针仍是未初始化的野指针,访问野指针直接触发段错误,这是本次崩溃的核心原因。 - 加载函数逻辑缺失:
load_data函数只完成了打开文件、计算文件大小、分配内存三步操作,既没有调用fread把权重、偏置文件内容读入内存,也没有调用fclose关闭文件句柄,就算指针正确也无法拿到有效数据,还会造成文件资源泄漏。 - 内存分配计算存在优先级问题:
malloc传入的参数如sizeof(unsigned char) * size_label - 8,由于乘法优先级高于减法,实际计算结果和预期一致,但写法存在歧义,若后续修改类型很容易出现分配长度错误。 - 指针偏移逻辑错误:
ar_img/ar_weight2等指针的类型是unsigned char*,C语言指针算术运算会自动按指向类型的宽度计算偏移,手动乘sizeof(char)/sizeof(float)会导致偏移量放大数倍,就算指针有效也会造成越界访问。另外直接把unsigned char*强转成float*取值,还可能触发内存对齐错误导致崩溃。 - 缺少异常校验:所有
fopen、malloc调用后都没有校验返回值是否为NULL,如果文件路径错误、内存分配失败,会直接操作空指针触发崩溃。 - 栈溢出隐患:主函数中定义的
float C[TRAIN_IMAGES_NUMBER]长度为60000,占约240KB栈空间,默认栈大小通常只有几MB,后续如果增加数组长度很容易触发栈溢出。
修复方案
- 修改加载函数的返回值或参数形式:把三个加载函数改为直接返回分配好的堆内存指针,或者传入二级指针作为参数,确保主函数中的指针能正确指向
malloc分配的内存地址。 - 补全
load_data函数逻辑:增加fread读取文件内容的步骤,读取完成后调用fclose关闭文件句柄。 - 修正内存分配计算:给长度减法增加括号明确计算优先级,例如
malloc(sizeof(unsigned char) * (size_label - 8)),避免歧义。 - 修正指针访问逻辑:访问
unsigned char类型的像素、标签数组时,直接用ar_img[i]按索引取值即可,不需要手动计算偏移;读取字节流中的float类型权重、偏置时,使用memcpy拷贝到float变量中,避免内存对齐问题。 - 增加异常校验:所有
fopen、malloc调用后检查返回值,如果返回NULL则打印错误信息并退出程序,避免空指针访问。 - 大数组改为堆分配:长度较大的数组如
C,使用malloc在堆上分配内存,或定义为全局变量存储在静态区,避免栈溢出。
关键修复代码示例
#include <string.h> // 引入memcpy头文件 // 修正后的图片加载函数,直接返回堆内存指针 unsigned char* load_train_images(char* path_train_images, int* out_total_size) { FILE *fp_img = fopen(path_train_images, "rb"); if (!fp_img) { perror("open train image file failed"); exit(EXIT_FAILURE); } fseek(fp_img, 0, SEEK_END); int total_size = ftell(fp_img); printf("%s size:%d byte\n", path_train_images, total_size); // 跳过16字节MNIST文件头 fseek(fp_img, 16, SEEK_SET); int data_len = total_size - 16; unsigned char* ar_img = (unsigned char*)malloc(sizeof(unsigned char) * data_len); if (!ar_img) { perror("malloc for image data failed"); exit(EXIT_FAILURE); } fread(ar_img, 1, data_len, fp_img); fclose(fp_img); *out_total_size = total_size; return ar_img; } // 主函数中调用加载函数的方式修正 int main(int argc, char *argv[]) { printf("Loading train images file.\n"); int size_img; unsigned char* ar_img = load_train_images(PATH_TRAIN_IMAGES, &size_img); // ... 其他加载函数按相同逻辑修改 // 修正输入层加载逻辑 for(int i = 0; i < PIXEL; i++) { in_units[i]->a = (float)ar_img[i]; printf("in_unit[%d] = %f\n", i, in_units[i]->a); } // 修正权重读取逻辑,用memcpy避免对齐问题 for(int i = 0; i < HIDDEN_UNITS_NUMBER; i++) { hid_units[i]->z = 0.0f; // 累加前重置z值 for(int j = 0; j < PIXEL; j++) { float w; memcpy(&w, ar_weight2 + (i*PIXEL + j)*sizeof(float), sizeof(float)); hid_units[i]->z += in_units[j]->a * w; } float b; memcpy(&b, ar_bias2 + i*sizeof(float), sizeof(float)); hid_units[i]->z += b; hid_units[i]->a = sigmod(hid_units[i]->z); } // ... 输出层计算按相同逻辑修正 // 用完后记得释放所有malloc的内存,包括hid_units、in_units、out_units里的每个Unit指针 return 0; }
内容的提问来源于stack exchange,提问作者Han
相关产品推荐
相关产品推荐

