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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 14:09:29