在这里插入图片描述

在C语言中,fseekfread 是标准I/O库(<stdio.h>)中用于文件操作的核心函数,而 ftell(而非 ftail)是另一个常用函数,用于获取当前文件指针的位置。以下是它们的详细说明、用法示例及在深度学习部署中的潜在应用场景。


1. fseek:文件指针定位(“翻到某一页”)

就像:直接翻到第 200 页,而不是一页一页翻

函数原型

int fseek(FILE *stream, long offset, int whence);
  • 功能:移动文件指针到指定位置。
  • 参数
    • stream:文件指针(通过 fopen 打开)。
    • offset:偏移量(字节单位,可为正/负)。
    • whence:基准位置(从哪里开始),可选:
      • SEEK_SET:文件开头。
      • SEEK_CUR:当前位置。
      • SEEK_END:文件末尾。
  • 返回值:成功返回 0,失败返回非零(如越界或文件未打开)。

eg:模型很大,只加载第 3 层 → 直接跳过去

示例代码

#include <stdio.h>

int main() {
    FILE *file = fopen("model.bin", "rb");
    if (!file) {
        perror("Failed to open file");
        return 1;
    }

    // 将文件指针移动到距开头100字节的位置
    if (fseek(file, 100, SEEK_SET) != 0) {
        perror("fseek failed");
        fclose(file);
        return 1;
    }

    // 从当前位置读取数据...
    fclose(file);
    return 0;
}

关键点

  • 二进制模式:深度学习模型文件(如 .bin.tflite)通常需以 "rb" 模式打开,避免换行符转换。
  • 错误处理:务必检查返回值,避免后续操作因指针位置无效而失败。
  • 大文件支持:在32位系统中,long 类型可能无法处理超过2GB的文件,此时可用 fseekooff_t(POSIX标准)。

2. fread:读取二进制数据(“读内容”)

把文件里的数据读到内存里

比如:把模型参数读出来

在深度学习里的作用:

  • 读取模型权重(weights)
  • 读取结构参数
  • 加载推理所需的数据

函数原型

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 功能:从文件读取数据到内存缓冲区。
  • 参数
    • ptr:目标内存缓冲区指针。
    • size:每个元素的大小(字节)。
    • nmemb:要读取的元素数量。
    • stream:文件指针。
  • 返回值:成功读取的元素数量(非字节数),若小于 nmemb,可能是文件结束或错误(需用 feofferror 判断)。

示例代码

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *file = fopen("weights.bin", "rb");
    if (!file) {
        perror("Failed to open file");
        return 1;
    }

    // 读取100个float类型数据(400字节)
    float *buffer = (float *)malloc(100 * sizeof(float));
    size_t read_count = fread(buffer, sizeof(float), 100, file);

    if (read_count != 100 && !feof(file)) {
        perror("fread failed");
        free(buffer);
        fclose(file);
        return 1;
    }

    printf("Read %zu floats\n", read_count);
    free(buffer);
    fclose(file);
    return 0;
}

关键点

  • 返回值:返回的是元素数量(如成功读取5个 float 则返回 5),而非字节数。
  • 内存分配:确保目标缓冲区足够大,避免溢出。
  • 性能优化:在嵌入式场景中,频繁小数据读取可能影响性能,建议一次性读取大块数据(如整个模型参数)。

3. ftell:获取当前文件指针位置(“现在在第几页?”)

获取当前读到哪里,用于计算数据位置,配合 fseek 做精确读取

函数原型

long ftell(FILE *stream);
  • 功能:返回当前文件指针的位置(相对于文件开头的偏移量,字节单位)。
  • 参数
    • stream:文件指针。
  • 返回值:成功返回当前位置(long 类型),失败返回 -1L

示例代码

#include <stdio.h>

int main() {
    FILE *file = fopen("data.bin", "rb");
    if (!file) {
        perror("Failed to open file");
        return 1;
    }

    // 移动指针并获取位置
    fseek(file, 100, SEEK_SET);
    long pos = ftell(file);
    printf("Current position: %ld\n", pos); // 输出: 100

    fclose(file);
    return 0;
}

关键点

  • 用途:常用于记录文件读取位置,便于后续恢复或调试。
  • 限制:在32位系统中,long 可能无法处理大文件(>2GB),此时可用 ftello(POSIX标准)。

4. 深度学习部署中的典型应用

三者配合

fseek(fp, offset, SEEK_SET);   // 跳到某一层的位置
fread(buffer, sizeof(float), N, fp);  // 读这一层参数
pos = ftell(fp);               // 记录当前位置

场景1:加载预训练模型权重

void load_model_weights(const char *filename, float *weights, int weight_count) {
    FILE *file = fopen(filename, "rb");
    if (!file) {
        fprintf(stderr, "Error opening model file\n");
        exit(1);
    }

    size_t read = fread(weights, sizeof(float), weight_count, file);
    if (read != weight_count) {
        fprintf(stderr, "Error reading weights (expected %d, got %zu)\n", weight_count, read);
        fclose(file);
        exit(1);
    }

    fclose(file);
}

场景2:跳过文件头读取特定数据

typedef struct {
    int version;
    int layer_count;
    // ...其他元数据
} ModelHeader;

void load_model_with_header(const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (!file) { /* 错误处理 */ }

    ModelHeader header;
    fread(&header, sizeof(ModelHeader), 1, file); // 读取文件头

    // 记录当前位置(可选)
    long header_end = ftell(file);
    printf("Header ends at byte: %ld\n", header_end);

    // 跳过某些部分,直接读取第3层权重
    fseek(file, 1024, SEEK_CUR); // 假设跳过1024字节

    float layer3_weights[256];
    fread(layer3_weights, sizeof(float), 256, file);

    fclose(file);
}

场景3:大文件分块读取

void load_large_file(const char *filename, size_t chunk_size) {
    FILE *file = fopen(filename, "rb");
    if (!file) { /* 错误处理 */ }

    void *buffer = malloc(chunk_size);
    if (!buffer) { /* 内存分配失败 */ }

    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, chunk_size, file)) > 0) {
        // 处理当前数据块(如写入内存或推理)
        process_chunk(buffer, bytes_read);
    }

    free(buffer);
    fclose(file);
}

🚀 核心流程图(嵌入式加载模型)

        📦 模型文件(model.bin)
                │
                ▼
        ┌───────────────┐
        │   打开文件     │ fopen
        └───────────────┘
                │
                ▼
        ┌───────────────┐
        │ fseek         │
        │ 👉 跳到某一层 │
        └───────────────┘
                │
                ▼
        ┌───────────────┐
        │ fread         │
        │ 👉 读取数据   │
        └───────────────┘
                │
                ▼
        ┌───────────────┐
        │ ftell         │
        │ 👉 记录位置   │
        └───────────────┘
                │
                ▼
        🔁 重复(读取下一层)
                │
                ▼
        ✅ 模型加载完成

🚀 为什么嵌入式必须这样做?

因为设备很弱👇

限制 影响
内存小 不能一次加载整个模型
CPU弱 不能处理太多数据
存储慢 读取要精准

👉 所以必须:“按需读取(用多少读多少)”

📦 模型文件结构图(带 offset)

model.bin(二进制模型文件)

┌──────────────────────────────┐
│ Header(文件头)              │  offset = 0
│ - magic number               │
│ - version                    │
│ - 层数 layer_num             │
└──────────────────────────────┘
                │
                ▼
┌──────────────────────────────┐
│ Layer 1 权重                 │  offset = 1024
│ (weights + bias)             │
└──────────────────────────────┘
                │
                ▼
┌──────────────────────────────┐
│ Layer 2 权重                 │  offset = 20480
│ (weights + bias)             │
└──────────────────────────────┘
                │
                ▼
┌──────────────────────────────┐
│ Layer 3 权重                 │  offset = 40960
│ (weights + bias)             │
└──────────────────────────────┘
                │
                ▼
            ……(更多层)

🔥 完整流程图(带函数)

        📦 model.bin
                │
                ▼
      offset = 20480(Layer 2)
                │
        ┌──────────────┐
        │ fseek        │
        │ 👉 跳位置    │
        └──────────────┘
                │
                ▼
        ┌──────────────┐
        │ fread        │
        │ 👉 读参数    │
        └──────────────┘
                │
                ▼
        ┌──────────────┐
        │ ftell        │
        │ 👉 看当前指针│
        └──────────────┘

👉 模型文件本质就是:

一段一段连续存储的数据块

👉 而 offset 就是:

每一段的“起始地址”

很多框架会这样做👇

Header 里存:
- 每一层的 offset
- 每一层的大小 size

比如:

Layer1: offset=1024, size=19456
Layer2: offset=20480, size=20480

👉 这样就可以:

fseek(fp, offset);
fread(..., size);

🔥完整代码

#include <stdio.h>
#include <stdlib.h>

#define MAX_LAYERS 10

// 每一层的信息(从文件头读出来)
typedef struct {
    int offset;   // 在文件中的位置
    int size;     // 数据大小(字节)
} LayerInfo;

// 模型头部结构
typedef struct {
    int layer_num;
    LayerInfo layers[MAX_LAYERS];
} ModelHeader;


// 👉 加载某一层(核心函数)
float* load_layer(FILE* fp, LayerInfo layer) {
    // 1️⃣ 跳到该层的位置
    fseek(fp, layer.offset, SEEK_SET);

    // 2️⃣ 分配内存
    float* buffer = (float*)malloc(layer.size);
    if (buffer == NULL) {
        printf("Memory allocation failed\n");
        return NULL;
    }

    // 3️⃣ 读取数据
    fread(buffer, 1, layer.size, fp);

    // 4️⃣ 打印当前文件位置(调试用)
    long pos = ftell(fp);
    printf("Read layer at offset %d, now at %ld\n", layer.offset, pos);

    return buffer;
}


int main() {
    // 1️⃣ 打开模型文件
    FILE* fp = fopen("model.bin", "rb");
    if (fp == NULL) {
        printf("Failed to open file\n");
        return -1;
    }

    // 2️⃣ 读取 Header
    ModelHeader header;
    fread(&header, sizeof(ModelHeader), 1, fp);

    printf("Layer num: %d\n", header.layer_num);

    // 3️⃣ 按需加载每一层
    for (int i = 0; i < header.layer_num; i++) {
        printf("Loading layer %d...\n", i);

        float* data = load_layer(fp, header.layers[i]);

        if (data == NULL) {
            fclose(fp);
            return -1;
        }

        // 👉 这里可以做推理(卷积/全连接等)
        // inference(data);

        // 4️⃣ 用完就释放(嵌入式必须这么干)
        free(data);
    }

    // 5️⃣ 关闭文件
    fclose(fp);

    return 0;
}

流程图(代码在干嘛)

        📦 model.bin
              │
              ▼
      读取 Header(层数 + offset)
              │
              ▼
        for 每一层:
              │
              ▼
        fseek 👉 跳到该层
              │
              ▼
        fread 👉 读取参数
              │
              ▼
        推理(用这层数据)
              │
              ▼
        free 👉 释放内存
              │
              ▼
        下一层...

很多真实项目会这样优化👇

// 只保留一块复用内存
float* buffer = malloc(MAX_LAYER_SIZE);

for (...) {
    fseek(...)
    fread(buffer, ...)
    inference(buffer)
}

👉 优点:

  • 不反复 malloc/free
  • 更快、更省内存

5. 常见问题与调试

问题1:fread 读取数量不匹配

  • 原因:文件末尾、权限不足或磁盘错误。
  • 解决:检查 feof(file)ferror(file) 区分原因。

问题2:fseek 后读取错误

  • 原因:未以二进制模式("rb")打开文件,导致换行符转换破坏偏移。
  • 解决:确保文件模式正确。

问题3:ftell 返回 -1

  • 原因:文件未打开或指针位置无效(如管道或终端设备)。
  • 解决:仅对可定位的文件(如普通文件)使用 ftell

问题4:大文件处理

  • 解决:在Linux中,使用 fseeko/ftellooff_t 类型支持大文件(>2GB)。

6. 总结

  • fseek 👉 跳位置:灵活定位文件指针,支持随机访问(如跳过模型元数据)。
  • fread 👉 读数据:高效读取二进制数据,需注意返回值和缓冲区管理。
  • ftell 👉 查位置:获取当前指针位置,便于调试或分块处理。
  • 优化建议
    • 大文件优先使用 mmapfseeko/ftello
    • 嵌入式场景中,结合 fseek 和块读取减少I/O次数。
    • 始终检查函数返回值以确保鲁棒性。

在很多推理框架(比如 TensorRT / TFLite / NCNN)中:

👉模型加载本质就是:

  • 文件定位(fseek)直接翻到第3章,而不是一页一页翻
  • 数据读取(fread)把第3章内容读进脑子(内存)
  • 偏移管理(ftell)看看现在读到哪一页了

“流式加载(streaming load)”

也就是:
👉 不一次吃完
👉 一口一口吃 🍜


函数 作用 类比
fread 读取数据 看书
fseek 跳到位置 翻页
ftell 当前进度 看页码

通过合理使用这三个函数,可以高效加载和处理深度学习模型文件,尤其在资源受限的环境中,精确控制文件读取行为对性能至关重要。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐