为文件创建自定义标头(元数据)
在这里,我想创建一个包含其他文件详细信息的标头,如其他文件的元数据。
如果我使用struct file_header
静态值,这段代码可以正常工作。 如果我使用malloc
for struct file_header
那么我在这段代码中遇到了问题。 具体来说,我遇到了一个问题。 也许fwrite
工作得很好。 代码在这里:
#include #include #include #include #include #include #include char path[1024] = "/home/test/main/Integration/testing/package_DIR"; //int count = 5; struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[5]; } metadata; metadata *create_header(); int main() { FILE *file = fopen("/home/test/main/Integration/testing/file.txt", "w"); metadata *header; header = create_header(); if(header != NULL) { printf("size of Header is %d\n",sizeof(header)); } if (file != NULL) { if (fwrite(&header, sizeof(header), 1, file) < 1) { puts("short count on fwrite"); } fclose(file); } file = fopen("/home/test/main/Integration/testing/file.txt", "rb"); if (file != NULL) { metadata header = { 0 }; if (fread(&header, sizeof(header), 1, file) d_type == DT_REG) { /* If the entry is a regular file */ header->file[file_count].file_name = (char *)malloc(sizeof(char)*strlen(entry->d_name)); strcpy(header->file[file_count].file_name,entry->d_name); //Put static but i have logic for this i will apply later. header->file[file_count].file_size = 10; file_count++; } } header->file_count = file_count; closedir(dirp); //printf("File Count : %d\n", file_count); return header; }
输出:
size of Header is 8 short count on fread File Name = (null) File count = 21918336 File Size = 0
有人可以帮我解决这个问题吗?
您正在使用64位计算机,因为您的指针长度为8个字节。
您正在尝试将数据写入文件,然后将其重新读入。您遇到了问题,因为指针写得不好。 (更准确地说:指针可以毫无问题地编写,但指针只在当前运行的程序中有意义,并且很少适合写入磁盘,甚至更少适合从磁盘读回。)
这部分代码说明了问题:
struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[5]; } metadata; metadata *create_header(); int main() { FILE *file = fopen("/home/test/main/Integration/testing/file.txt", "w"); metadata *header; header = create_header(); if(header != NULL) { printf("size of Header is %d\n",sizeof(header)); }
附注:
- 将文件名作为
main()
的参数,或者至少变为变量。 将名称写出两次使得很难改变。 - 你正在做一些错误检测是好的。 但是,我不打算批评它,尽管它有很大的改进空间。
主要评论:
-
你看到
size of Header is 8
在输出中size of Header is 8
,因为header
是一个指针。sizeof(metadata)
(header
指向的类型)要大得多,可能是48个字节,但这取决于编译器如何在结构中对齐和打包数据。if (file != NULL) { if (fwrite(&header, sizeof(header), 1, file) < 1) { puts("short count on fwrite"); } fclose(file); }
此代码将8个字节的数据写入文件。 它写的是存储header
变量的地址。 它不会写出它指向的任何数据。
什么会更接近你所追求的(但仍然不会起作用)是:
if (fwrite(header, sizeof(*header), 1, file) < 1) { puts("short count on fwrite"); }
这将写入48个字节或其左右的文件。 但是,您的文件不包含文件名; 它只包含指向文件编写时文件名存储位置的指针。 这里要非常小心。 如果您阅读此文件,您甚至可能会看到它似乎正常工作,因为名称可能尚未从内存中删除。
要将文件名放入文件中,您必须单独处理每个文件名。 你必须决定一个约定。 例如,您可能会确定该名称将以2字节unsigned short
作为前缀,其中包含文件名L的长度,后跟L + 1个字节的数据,其中包含文件名及其终端NUL '\0'
。 然后,您将编写每个文件数据的其他(固定大小)部分。 并且您将为每个文件重复此过程。 读取文件的逆操作需要理解书面结构。 在您期望文件名的位置,您将读取两个字节的长度,并且可以使用该长度为文件名分配空间。 然后将L + 1个字节读入新分配的文件名中。 然后,您读取该文件的其他固定长度数据,然后转到下一个文件。
如果你想在一个fwrite()
然后fread()
,你将不得不修改你的数据结构:
struct files { char file_name[MAX_PERMITTED_FILENAME_LENGTH]; int file_size; };
您可以决定允许的最大文件名长度是多少,但它是固定的。 如果你的名字很短,你就不会占用所有的空间; 如果你的名字很长,它们可能会被截断。 您的metadata
结构大小现在显着增加(至少如果MAX_PERMITTED_FILENAME_LENGTH
是合理的大小,比如介于32和1024字节之间)。 但是您可以使用它在一个操作中读取和写入整个metadata
结构。
感谢您的回复,但我是C新手,那么我怎么能实现这个目标呢?
最终,你将能够像这样编码它。
#include #include #include #include #include #include enum { MAX_FILES = 5 }; struct files { char *file_name; int file_size; }; typedef struct file_header { int file_count; struct files file[MAX_FILES]; } metadata; static void err_exit(const char *format, ...); static metadata *create_header(const char *directory); static void release_header(metadata *header); static void write_header(FILE *fp, const metadata *header); static metadata *read_header(FILE *fp); static void dump_header(FILE *fp, const char *tag, const metadata *header); int main(int argc, char **argv) { if (argc != 3) err_exit("Usage: %s file directory\n", argv[0]); const char *name = argv[1]; const char *path = argv[2]; FILE *fp = fopen(name, "wb"); if (fp == 0) err_exit("Failed to open file %s for writing (%d: %s)\n", name, errno, strerror(errno)); metadata *header = create_header(path); dump_header(stdout, "Data to be written", header); write_header(fp, header); fclose(fp); // Ignore error on close release_header(header); if ((fp = fopen(name, "rb")) == 0) err_exit("Failed to open file %s for reading (%d: %s)\n", name, errno, strerror(errno)); metadata *read_info = read_header(fp); dump_header(stdout, "Data as read", read_info); release_header(read_info); fclose(fp); // Ignore error on close return 0; } static metadata *create_header(const char *path) { int file_count = 0; DIR * dirp = opendir(path); struct dirent * entry; if (dirp == 0) err_exit("Failed to open directory %s (%d: %s)\n", path, errno, strerror(errno)); metadata *header = (metadata *)malloc(sizeof(metadata)); if (header == 0) err_exit("Failed to malloc space for header (%d: %s)\n", errno, strerror(errno)); header->file_count = 0; while ((entry = readdir(dirp)) != NULL && file_count < MAX_FILES) { // d_type is not portable - POSIX says you can only rely on d_name and d_ino if (entry->d_type == DT_REG) { /* If the entry is a regular file */ // Avoid off-by-one under-allocation by using strdup() header->file[file_count].file_name = strdup(entry->d_name); if (header->file[file_count].file_name == 0) err_exit("Failed to strdup() file %s (%d: %s)\n", entry->d_name, errno, strerror(errno)); //Put static but i have logic for this i will apply later. header->file[file_count].file_size = 10; file_count++; } } header->file_count = file_count; closedir(dirp); //printf("File Count : %d\n", file_count); return header; } static void write_header(FILE *fp, const metadata *header) { if (fwrite(&header->file_count, sizeof(header->file_count), 1, fp) != 1) err_exit("Write error on file count (%d: %s)\n", errno, strerror(errno)); const struct files *files = header->file; for (int i = 0; i < header->file_count; i++) { unsigned short name_len = strlen(files[i].file_name) + 1; if (fwrite(&name_len, sizeof(name_len), 1, fp) != 1) err_exit("Write error on file name length (%d: %s)\n", errno, strerror(errno)); if (fwrite(files[i].file_name, name_len, 1, fp) != 1) err_exit("Write error on file name (%d: %s)\n", errno, strerror(errno)); if (fwrite(&files[i].file_size, sizeof(files[i].file_size), 1, fp) != 1) err_exit("Write error on file size (%d: %s)\n", errno, strerror(errno)); } } static metadata *read_header(FILE *fp) { metadata *header = malloc(sizeof(*header)); if (header == 0) err_exit("Failed to malloc space for header (%d:%s)\n", errno, strerror(errno)); if (fread(&header->file_count, sizeof(header->file_count), 1, fp) != 1) err_exit("Read error on file count (%d: %s)\n", errno, strerror(errno)); struct files *files = header->file; for (int i = 0; i < header->file_count; i++) { unsigned short name_len; if (fread(&name_len, sizeof(name_len), 1, fp) != 1) err_exit("Read error on file name length (%d: %s)\n", errno, strerror(errno)); files[i].file_name = malloc(name_len); if (files[i].file_name == 0) err_exit("Failed to malloc space for file name (%d:%s)\n", errno, strerror(errno)); if (fread(files[i].file_name, name_len, 1, fp) != 1) err_exit("Read error on file name (%d: %s)\n", errno, strerror(errno)); if (fread(&files[i].file_size, sizeof(files[i].file_size), 1, fp) != 1) err_exit("Read error on file size (%d: %s)\n", errno, strerror(errno)); } return(header); } static void dump_header(FILE *fp, const char *tag, const metadata *header) { const struct files *files = header->file; fprintf(fp, "Metadata: %s\n", tag); fprintf(fp, "File count: %d\n", header->file_count); for (int i = 0; i < header->file_count; i++) fprintf(fp, "File %d: size %5d, name %s\n", i, files[i].file_size, files[i].file_name); } static void release_header(metadata *header) { for (int i = 0; i < header->file_count; i++) { /* Zap file name, and pointer to file name */ memset(header->file[i].file_name, 0xDD, strlen(header->file[i].file_name)+1); free(header->file[i].file_name); memset(&header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); } free(header); } static void err_exit(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(EXIT_FAILURE); }
我将其编译为dump_file
,并按如下所示运行它:
$ dump_file xyz . Metadata: Data to be written File count: 5 File 0: size 10, name .gitignore File 1: size 10, name args.c File 2: size 10, name atob.c File 3: size 10, name bp.pl File 4: size 10, name btwoc.c Metadata: Data as read File count: 5 File 0: size 10, name .gitignore File 1: size 10, name args.c File 2: size 10, name atob.c File 3: size 10, name bp.pl File 4: size 10, name btwoc.c $ odx xyz 0x0000: 05 00 00 00 0B 00 2E 67 69 74 69 67 6E 6F 72 65 .......gitignore 0x0010: 00 0A 00 00 00 07 00 61 72 67 73 2E 63 00 0A 00 .......args.c... 0x0020: 00 00 07 00 61 74 6F 62 2E 63 00 0A 00 00 00 06 ....atob.c...... 0x0030: 00 62 70 2E 70 6C 00 0A 00 00 00 08 00 62 74 77 .bp.pl.......btw 0x0040: 6F 63 2E 63 00 0A 00 00 00 oc.c..... 0x0049: $
我应该将err_exit()
重命名为err_sysexit()
并修改error handling,以便在该函数内处理errno
和相应的字符串,而不是重复地将errno
和strerror(errno)
到对err_exit()
的调用中。
来自评论的信息
将一些相当广泛的评论转移到问题中:
我尝试了上面的代码,但是在
File : 4
之后得到了分段错误,这意味着数据写入工作正常但我在数据读取方面遇到了一些问题。 Nimit我尝试了上面的代码,当我从文件中读取数据时,我遇到了分段错误。 user1089679
糟糕: valgrind
在release_header()
给出了关于写入无效的警告。 这会搞砸了。 不过难以解决 - 它是release_header()
中的第二个memset()
导致恶作剧; 我不小心省略了&符号:
memset( header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); // Broken memset(&header->file[i].file_name, 0xEE, sizeof(header->file[i].file_name)); // Correct
这已在代码中修复。 请注意,两个memset()
操作都在代码中,以确保如果重用内存,它不包含以前的有效数据,这是一个风险,因为代码最初将指针写入磁盘然后再次读回它们。 memset()
调用不会出现在普通的生产代码中。
请注意, odx
是一个家庭式hex转储程序(Mac OS X默认没有hd
程序)。 您的系统可能已经有hd
for hex dump,或者您可以尝试高清或尝试使用自己的Google Fu来寻找替代方案。
只是想问一下,我想在跨平台上运行这个程序,那么低位机器有什么问题吗? Nimit
这个代码在big-endian或little-endian机器上没有问题; 如果将数据从小端(Intel)机器传输到big-endian(SPARC,PPC,...)机器或反之亦然,则会出现问题。 代码可能对32位和64位版本也很敏感; 我没有将字段大小定义为n位,而是将类似int的方便类型定义为可在系统之间更改。 如果你想要可移植数据,决定字段大小(主要是1,2,4,8字节,至少是非字符串数据),然后以标准方式写入 - MSB优先(big-endian)或也许LSB第一(小端)。