【问题标题】:reading unknown length file in buffer chunks in c在c中的缓冲区块中读取未知长度的文件
【发布时间】:2018-09-28 23:57:30
【问题描述】:

我正在尝试不使用lseek()fseek 等函数将未知长度的二进制文件读入缓冲区块。

  1. 我使用了一次有 1024 个字节的结构缓冲区。当读取大于 1012 字节的文件时,它将分配几个缓冲区。但是,当它遇到最后一个块时,它肯定会小于或等于 1024 字节。 因此,我尝试计算最后一个块的长度,以便我可以读取最后一个块直到eof,但我对如何实现这一点有点困惑。

提前致谢。

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

typedef struct Buffer{
  unsigned char data[1012];
  struct Buffer *next; //12 bytes
}Buffer;

void mymemcpy(void *dest, void *src, size_t length){
  Buffer *buffer_toFill = (Buffer *)dest;
  Buffer *buffer_toAdd = (Buffer *)src;
  int a = 0; 
  for(int i = 0; i < length; i++){
    buffer_toFill->data[i] = buffer_toAdd->data[i];
  }
}

Buffer* add_buffer_front(Buffer *head, Buffer *read_buffer, int size){
  Buffer *new_buffer = malloc(sizeof(Buffer));
  mymemcpy(new_buffer, read_buffer, size);
  if(head != NULL){
    new_buffer->next = head;
  }
  return new_buffer;
}

void display_List(Buffer *head, size_t length){
  Buffer *current = head;
  while(current != NULL){
    for(int i = 0; i < length; i++){
      printf("%02X",(unsigned)current->data[i]); //this shows different value compare with  xxd <filename>
      //printf("%c", current->data[i]);  
    }
    Buffer *prev = current;
    free(prev);
    current = current->next;
  }
}

int main(int argc, char **argv){
  FILE *fd;
  Buffer *head_buffer = NULL;
  int file_length = 0;
  int eof_int = 1;
  if(argc != 2){
    printf("Usage: readFile <filename>\n");
    return 1; 
  }

  fd = fopen(argv[1], "rb");

  while(eof_int != 0){ 
    Buffer *new_buffer = malloc(sizeof(Buffer));
    eof_int = fread(new_buffer, sizeof(Buffer)-12, 1, fd);
    if(eof_int == 0){ 
      //size_t length
      //
      //
      head_buffer = add_buffer_front(head_buffer, new_buffer, length);
      file_length += length;
    }else{
      head_buffer = add_buffer_front(head_buffer, new_buffer, (sizeof(new_buffer->data)));
      file_length += (sizeof(new_buffer->data));
    }
  }
  display_List(head_buffer, file_length);
  fclose(fd);
  return 0;
}

【问题讨论】:

  • 为什么不一口气把整个文件读入一个缓冲区呢?只需先使用stat 找出它的长度,分配足够的空间并读取它。
  • 很遗憾,它不能用 stat() 函数实现。我真的很困惑如何避免在最后一块打印空字节。
  • 正如 Ed Heal 所说,使用 stat 查找文件的长度,然后一次读取所有文件。尝试编写您拥有的代码对您有好处,但它非常复杂并且难以维护。首选更简单的解决方案,因为有可用的解决方案。
  • 为什么不能使用stat函数?
  • 你必须使用这种链接缓冲区的方法吗?你能用realloc吗?实际任务是什么?

标签: c


【解决方案1】:

你有几个问题。

(1) fread 返回读取的项目数,但不会返回 eof 指示。您需要致电feof(stream*) 以了解您是否已到达文件末尾。

(2) 你说你的下一个指针是 12 个字节。这是一个非常危险的假设。更喜欢读取分配给数据结构的 1012 个字节。您当前很可能正在打印未读入的内容,而只是未初始化的内存。

(3) 使用来自fread 的返回值来决定要复制多少内存。

【讨论】:

  • 发帖者也错过了检查 fopen 返回值
  • 请注意,fread 大小和项目参数需要颠倒 #3 以返回读取了多少字节而不是读取了多少缓冲区。 fread(new_buffer, 1, sizeof(new_buffer-&gt;data), fd);
  • 注意there are issues with using feof。最好检查while( fread &lt; 0 ) 并使用ferror 来确定循环是否由于错误或文件结束而退出。
【解决方案2】:

请查看下面代码中的 cmets - 还可以考虑将 1012 更改为使用 #define。

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

typedef struct Buffer{
  unsigned char data[1012];
  struct Buffer *next; //12 bytes
}Buffer;

// Create a structure to store stuff about a file

typedef struct {
   Buffer *head;
   Buffer *tail;
   size_t length;
} MyFile;

/*
void mymemcpy(void *dest, void *src, size_t length){
  Buffer *buffer_toFill = (Buffer *)dest;
  Buffer *buffer_toAdd = (Buffer *)src;
  int a = 0; 
  for(int i = 0; i < length; i++){
    buffer_toFill->data[i] = buffer_toAdd->data[i];
  }
}

Buffer* add_buffer_front(Buffer *head, Buffer *read_buffer, int size){
  Buffer *new_buffer = malloc(sizeof(Buffer));
  mymemcpy(new_buffer, read_buffer, size);
  if(head != NULL){
    new_buffer->next = head;
  }
  return new_buffer;
}

*/

// Lets make this easier - The buffer has already been "malloced" once - why do it again

// And why are you reversing the file

// Perhaps 

void add_buffer(Buffer *to_be_added, MyFile *file, size_t extra_length) {
   if (file->tail) { // We have one item in the list
     file->tail->next = to_be_added;
   } else { // First buffer!
     file-> head = to_be_added;
     file-> tail = to_be_added;
   }
   to_be_added->next = NULL;  // This is always the case as it is the last one
   file->length += extra_length;
}

/*
void display_List(Buffer *head, size_t length){
  Buffer *current = head;
  while(current != NULL){
    for(int i = 0; i < length; i++){
      printf("%02X",(unsigned)current->data[i]); //this shows different value compare with  xxd <filename>
      //printf("%c", current->data[i]);  
    }
    Buffer *prev = current;
    free(prev);
    current = current->next;
  }
}

*/

// Instead pass in the new structure

void display_list(MyFile *file) {
   size_t contents_left = file -> length;
   Buffer * current = file -> head;
   while (current) {
      // At most each chunk has 1012 bytes - Check for that
      size_t chunk_length = contents_left > 1012 ? 1012 : contents_left;
       for(int i = 0; i <chunk_length ; i++){
         printf("%02X",(unsigned)current->data[i]);
       }
       current = current -> next;
   }
}


}
int main(int argc, char **argv){
  FILE *fd;
  MyFile read_file;
  read_file.head = NULL;
  read_file.tail = NULL;
  read_file.length = 0;

  Buffer *head_buffer = NULL;
  int file_length = 0;
  int eof_int = 1;
  if(argc != 2){
    printf("Usage: readFile <filename>\n");
    return 1; 
  }

  fd = fopen(argv[1], "rb");

  // Check fd
  if (fd == NULL) {
    // error stuff
    return EXIT_FAILURE; // Look up the include for this
 }
  while(eof_int != 0){ 
    Buffer *new_buffer = malloc(sizeof(Buffer));
    eof_int = fread(new_buffer->data, 1012, 1, fd); // Do not make assumptions on the size of a pointer and store it in the correct location
    if(eof_int == 0) { // Read nothing
       free(new_buffer); // We was too optimistic! Did Not need this in the end 
       break;
    } else {
      add_buffer(&read_file, new_buffer, eof_int);
    }
  }
  display_List(&read_file);
  fclose(fd);
  return 0;
}

【讨论】:

    【解决方案3】:

    您正在寻找的技巧是 fread 返回读取的项目数。您正在读取 1 个缓冲区已满,因此它只会告诉您您读取了 0 个或 1 个缓冲区。相反,翻转它并反转它:读取缓冲区的字节数

    size_t bytes_read = fread(buffer, 1, sizeof(Buffer)-12, fd);
    

    现在您可以知道有多少字节读入了您的缓冲区。我们可以在Buffer 中添加一个size 字段,这样每个缓冲区就可以记住它读取了多少字节,并且只打印那个。

    const size_t BUFFER_SIZE = 1024;
    
    typedef struct Buffer {
        // I'll explain why I switched to a pointer in a moment
        unsigned char *data;
        size_t size;
        struct Buffer *next;
    } Buffer;
    
    void Buffer_print( Buffer *buffer ) {
        for( size_t i = 0; i < buffer->size; i++ ) {
            printf("%02hhX ", buffer->data[i]);
        }
    }
    
    Buffer *Buffer_new() {
        Buffer *buffer = malloc(sizeof(Buffer));
    
        buffer->size = 0;
        buffer->data = NULL;
        buffer->next = NULL;
    
        return buffer;
    }
    

    请注意,我会小心初始化缓冲区的所有字段,否则我们可能会遇到垃圾。

    现在我们已经改变了我们的缓冲区,所以我们关于它的大小和位置的假设被打破了。没关系,我们应该直接读到buffer-&gt;data

    size_t Buffer_read( Buffer *buffer, size_t buffer_size, FILE* fp ) {
        buffer->data = malloc(buffer_size);
        size_t bytes_read = fread(buffer->data, 1, buffer_size, fp);
        buffer->size = bytes_read;
        return bytes_read;
    }
    

    既然缓冲区知道它读取了多少数据,我们可以根据需要分配任何大小的数据。无需将其硬编码到结构中。这使代码更加灵活和高效。它让我们可以廉价地分配空缓冲区,这将使事情变得更加简单。

    我们也可以使用malloc 并让buffer-&gt;data 用垃圾初始化。如果fread 仅部分填充buffer-&gt;data 其余部分将仍然是垃圾。没关系,知道我们读取的数据的大小意味着我们在遇到垃圾之前停止打印。


    现在我们可以构建我们的循环了。当它读取 0 字节时,我们知道它已完成读取。

    while( Buffer_read( buffer, BUFFER_SIZE, fp ) > 0 ) {
        ... now what ...
    }
    fclose(fp);
    

    链接列表的工作方式,当您添加到列表时,您添加到 tail-&gt;next 并使其成为新的尾部。这通常被称为“推动”。

    Buffer *Buffer_push( Buffer *tail, Buffer *new_tail ) {
        tail->next = new_tail;
        return new_tail;
    }
    
    Buffer *head = Buffer_new();
    Buffer *tail = head;
    while( Buffer_read( tail, BUFFER_SIZE, fp ) > 0 ) {
        tail = Buffer_push( tail, Buffer_new() );
    }
    fclose(fp);
    

    请注意,我们以空的head 开头,这也是tail。从这两个分配开始使循环更简单。无需检查if( head )if( tail )。这确实意味着我们的列表末尾总是有一个空缓冲区。没关系。由于我们不再使用固定的buffer-&gt;data,因此空缓冲区现在很小且便宜。


    最后一步是打印所有内容。我们已经可以打印单个缓冲区了,所以我们只需要遍历链表并打印每个缓冲区。

    void Buffer_print_all( Buffer *head ) {
        for( Buffer *buffer = head; buffer != NULL; buffer = buffer->next ) {
            Buffer_print(buffer);
        }
    }
    
    Buffer_print_all(head);
    

    挂在末尾的最后一个空缓冲区很好。它知道它的大小为 0,所以 Buffer_print 实际上不会使用 null buffer-&gt;data

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-23
    • 2015-08-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-12
    • 2013-09-19
    相关资源
    最近更新 更多