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

使用fwrite生成.tar文件时数据被截断,求技术排查

修复.tar文件生成时数据截断与格式错误的问题

我写了一段C代码,用来把两个.txt文件打包成简化版.tar,但生成的文件损坏,数据被截断。调试时读取的内容正常,推测问题出在写入环节。

原代码

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

#define  RECORDSIZE  512
#define  NAMSIZ      100
#define  TUNMLEN      32
#define  TGNMLEN      32

struct header {
    char name[NAMSIZ];//needed
    char size[12];//needed
};

int main(int argc, char** argv) {
    //argv[1] file1 argv[2] file2
    char* file1 = argv[1];
    char* file2 = argv[2];
    FILE* f;
    int lSize;
    char temp_length[10];
    char* file1_data, * file2_data;
    int result;

    //char* output_str = (char*)malloc(sizeof)

    f = fopen(file1, "rb");
    if (f == NULL) {
        printf("File error!");
        return 1;
    }
    fseek(f, 0, SEEK_END);
    lSize = ftell(f);
    fseek(f, 0, SEEK_SET);
    file1_data = (char*)malloc(sizeof(char) * lSize);
    if (file1_data == NULL) {
        printf("Memory error!");
        return 1;
    }
    result = fread(file1_data, 1, lSize, f);
    file1_data[result] = '\0';
    fclose(f);
    sprintf(temp_length, "%d", lSize);
    struct header* h1 = malloc(sizeof(struct header));
    strcpy(h1->name, file1);
    strcpy(h1->size, temp_length);
    printf("Name:%s Value:%s\n", h1->name, h1->size);
    printf("File 1 data:%s\n", file1_data);


    f = fopen(file2, "rb");
    if (f == NULL) {
        printf("File error!");
        return 1;
    }
    fseek(f, 0, SEEK_END);
    lSize = ftell(f);
    fseek(f, 0, SEEK_SET);
    file2_data = (char*)malloc(sizeof(char) * lSize);
    if (file2_data == NULL) {
        printf("Memory error!");
        return 1;
    }
    result = fread(file2_data, 1, lSize, f);
    file2_data[result] = '\0';
    fclose(f);
    sprintf(temp_length, "%d", lSize);
    struct header* h2 = malloc(sizeof(struct header));
    strcpy(h2->name, file1);
    strcpy(h2->size, temp_length);
    
    printf("Name:%s Value:%s\n", h2->name, h2->size);
    printf("File 2 data:%s\n", file2_data);

    //allocate mem for output buffer

    int total = sizeof(struct header) + sizeof(struct header) + sizeof(file1_data) + sizeof(file2_data);
    printf("total length %d\n", total);

    f = fopen("Result.tar", "wb");

    //fwrite(input,length,no of ele,output buffer)
    fwrite(h1, sizeof(struct header), 1, f);
    fwrite(file1_data, sizeof(file1_data), 1, f);
    fwrite(h2, sizeof(struct header), 1, f);
    fwrite(file2_data, sizeof(file2_data), 1, f);
    if (fwrite != 0)
        printf("Contents to file written successfully !\n");
    else
        printf("Error writing file !\n");
    fclose(f);
}

核心错误分析

  • 文件数据写入长度错误:使用sizeof(file1_data)获取文件数据长度,但file1_data是指针,sizeof返回的是指针本身的大小(32位系统4字节,64位系统8字节),导致实际只写入了几个字节的文件数据,直接造成截断。
  • 第二个文件头部名称错误:strcpy(h2->name, file1);把第一个文件名复制给了第二个文件的头部,属于逻辑错误。
  • tar格式不兼容:
    • tar的size字段要求用八进制字符串表示,并且要填充空格到12位,代码里用了十进制字符串,不符合格式规范。
    • tar的每个头部和数据块必须对齐到512字节,代码里没有做对齐处理,会导致后续文件解析失败。
  • 内存越界访问:file1_data[result] = '\0';,malloc的大小是lSize,result最大等于lSize,此时访问file1_data[result]属于越界,会导致未定义行为。
  • 写入成功判断错误:if (fwrite != 0)是在判断函数指针是否为空,完全错误,应该检查每个fwrite的返回值是否等于预期写入的元素数。

修正后的代码

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

#define RECORDSIZE  512
#define NAMSIZ      100
#define TUNMLEN     32
#define TGNMLEN     32

struct header {
    char name[NAMSIZ];
    char size[12];
    // 填充字段,确保头部总长度为512字节
    char padding[RECORDSIZE - NAMSIZ - 12];
};

// 计算需要填充的字节数,对齐到512字节
int calculate_pad(int length) {
    int remainder = length % RECORDSIZE;
    return remainder == 0 ? 0 : RECORDSIZE - remainder;
}

int main(int argc, char** argv) {
    if (argc != 3) {
        printf("Usage: %s <file1> <file2>\n", argv[0]);
        return 1;
    }

    char* file1 = argv[1];
    char* file2 = argv[2];
    FILE* f;
    int lSize1, lSize2;
    char* file1_data = NULL, * file2_data = NULL;
    struct header h1 = {0}, h2 = {0};
    int pad1, pad2;

    // 读取第一个文件
    f = fopen(file1, "rb");
    if (f == NULL) {
        printf("File error: %s\n", file1);
        return 1;
    }
    fseek(f, 0, SEEK_END);
    lSize1 = ftell(f);
    fseek(f, 0, SEEK_SET);
    file1_data = (char*)malloc(lSize1);
    if (file1_data == NULL) {
        printf("Memory error!\n");
        fclose(f);
        return 1;
    }
    if (fread(file1_data, 1, lSize1, f) != lSize1) {
        printf("Read error: %s\n", file1);
        fclose(f);
        free(file1_data);
        return 1;
    }
    fclose(f);

    // 填充第一个文件的头部
    strncpy(h1.name, file1, NAMSIZ - 1); // 防止字符串溢出
    // 把文件大小转为八进制字符串,符合tar格式要求
    snprintf(h1.size, 12, "%o", lSize1);
    // 补空格到12位
    int len = strlen(h1.size);
    memset(h1.size + len, ' ', 12 - len);

    // 读取第二个文件
    f = fopen(file2, "rb");
    if (f == NULL) {
        printf("File error: %s\n", file2);
        free(file1_data);
        return 1;
    }
    fseek(f, 0, SEEK_END);
    lSize2 = ftell(f);
    fseek(f, 0, SEEK_SET);
    file2_data = (char*)malloc(lSize2);
    if (file2_data == NULL) {
        printf("Memory error!\n");
        fclose(f);
        free(file1_data);
        return 1;
    }
    if (fread(file2_data, 1, lSize2, f) != lSize2) {
        printf("Read error: %s\n", file2);
        fclose(f);
        free(file1_data);
        free(file2_data);
        return 1;
    }
    fclose(f);

    // 填充第二个文件的头部
    strncpy(h2.name, file2, NAMSIZ - 1);
    snprintf(h2.size, 12, "%o", lSize2);
    len = strlen(h2.size);
    memset(h2.size + len, ' ', 12 - len);

    // 计算需要的填充字节数
    pad1 = calculate_pad(lSize1);
    pad2 = calculate_pad(lSize2);
    // tar文件末尾需要两个空的512字节块
    char empty_block[RECORDSIZE] = {0};

    // 写入tar文件
    f = fopen("Result.tar", "wb");
    if (f == NULL) {
        printf("Cannot create output file!\n");
        free(file1_data);
        free(file2_data);
        return 1;
    }

    // 写入第一个文件头部和数据
    if (fwrite(&h1, sizeof(struct header), 1, f) != 1) {
        printf("Write header1 error!\n");
        goto cleanup;
    }
    if (fwrite(file1_data, 1, lSize1, f) != lSize1) {
        printf("Write file1 data error!\n");
        goto cleanup;
    }
    if (pad1 > 0 && fwrite(empty_block, 1, pad1, f) != pad1) {
        printf("Write pad1 error!\n");
        goto cleanup;
    }

    // 写入第二个文件头部和数据
    if (fwrite(&h2, sizeof(struct header), 1, f) != 1) {
        printf("Write header2 error!\n");
        goto cleanup;
    }
    if (fwrite(file2_data, 1, lSize2, f) != lSize2) {
        printf("Write file2 data error!\n");
        goto cleanup;
    }
    if (pad2 > 0 && fwrite(empty_block, 1, pad2, f) != pad2) {
        printf("Write pad2 error!\n");
        goto cleanup;
    }

    // 写入末尾的两个空块
    if (fwrite(empty_block, RECORDSIZE, 2, f) != 2) {
        printf("Write empty blocks error!\n");
        goto cleanup;
    }

    printf("Contents to file written successfully !\n");

cleanup:
    fclose(f);
    free(file1_data);
    free(file2_data);
    return 0;
}

关键修正说明

  1. 修复数据写入长度:用实际读取的文件大小lSize1、lSize2替代sizeof(file1_data),确保写入完整的文件内容。
  2. 修正文件名赋值:第二个文件头部正确使用file2作为文件名。
  3. 兼容tar格式:
    • 把文件大小转为八进制字符串,填充空格到12位,符合tar规范。
    • 给头部结构体添加填充字段,确保每个头部是512字节;计算数据块的填充字节,保证每个文件数据块对齐到512字节;在文件末尾添加两个空的512字节块,这是tar格式的强制要求。
  4. 消除内存越界:去掉了给file_data添加'\0'的操作,避免越界访问,同时二进制文件不需要额外的字符串结束符。
  5. 正确判断写入结果:每个fwrite调用后检查返回值是否等于预期写入的数量,确保写入操作成功。

内容的提问来源于stack exchange,提问作者tthh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 21:01:09