使用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字节,代码里没有做对齐处理,会导致后续文件解析失败。
- tar的
- 内存越界访问:
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; }
关键修正说明
- 修复数据写入长度:用实际读取的文件大小
lSize1、lSize2替代sizeof(file1_data),确保写入完整的文件内容。 - 修正文件名赋值:第二个文件头部正确使用
file2作为文件名。 - 兼容tar格式:
- 把文件大小转为八进制字符串,填充空格到12位,符合tar规范。
- 给头部结构体添加填充字段,确保每个头部是512字节;计算数据块的填充字节,保证每个文件数据块对齐到512字节;在文件末尾添加两个空的512字节块,这是tar格式的强制要求。
- 消除内存越界:去掉了给
file_data添加'\0'的操作,避免越界访问,同时二进制文件不需要额外的字符串结束符。 - 正确判断写入结果:每个
fwrite调用后检查返回值是否等于预期写入的数量,确保写入操作成功。
内容的提问来源于stack exchange,提问作者tthh
相关产品推荐
相关产品推荐

