fread() 中极其奇怪的行为;打印字符串时读取整个字符串,但说它没有达到 EOF,而当它没有达到 EOF 时

Extremely weird behavior in fread(); read entire string when printing string but says it did not reached EOF and reaches EOF when it did not

我在以下函数中遇到了一些奇怪的行为:

char* read_file(const char* filename) {
  FILE* file = fopen(filename, "rb"); // error check

  fseek(file, 0L, SEEK_END); // error check this too
  unsigned int size = ftell(file); // error check

  rewind(file);

  // I know a char is a single byte, but just for readibility's sake:

  char* buffer = malloc(sizeof(char) * size + 1); // error check

  unsigned int result = fread(buffer, sizeof(char), size, file); // more error check

  fclose(file);
  // puts(buffer); // see the rest of the question, this is line 16
  return buffer;
}

如果第 16 行保持注释,我的程序中出现未定义的行为,即返回的字符串不是我预期的,即发生错误。通过错误检查,我避免了这个问题,我得到 result 等于 size,所以 fread 成功读取了文件。现在,如果我取消注释第 16 行并打印结果,一切正常并且不会发生未定义的行为。为什么会发生这种情况?

malloc 分配未初始化的存储,这意味着返回缓冲区的内容未初始化为零。

您分配 size + 1 字节的缓冲区,然后从文件中读取 size 字节。缓冲区中的最后一个字节,你因为 + 1 而得到的字节?你从来没有给它写过任何东西。该字符串缺少空终止符,fread 不会为您这样做。

您必须自己添加空终止符,否则 puts 将在尝试打印它时遇到未定义的行为:

buffer[result] = '[=10=]';

除了 好的答案,OP 的代码还有其他弱点。

不检查是否打开成功

  FILE* file = fopen(filename, "rb"); // error check
  
  // add
  if (file == NULL) {
    fprintf(stderr, "Unable t open file <%s>\n", filename);
    return EXIT_FAILURE;
  }  

无错误检查,不需要L

  //fseek(file, 0L, SEEK_END); // error check this too

  if (fseek(file, 0, SEEK_END)) {
    // add error message here
    return EXIT_FAILURE;
  }

类型错误,未进行错误检查

  // unsigned int size = ftell(file);

  // ftell returns a long
  long size = ftell(file);
  if (size == -1) {
    // add error message here
    return EXIT_FAILURE;
  }

  rewind(file);  // this is OK

无大小错误检查、概念错误乘法、无分配检查

  // char* buffer = malloc(sizeof(char) * size + 1); // error check

  if (size < 0 || size >= SIZE_MAX) {
    fprintf(stderr, "Size %ld was to large to convert to size_t\n", size);
    return EXIT_FAILURE;
  }

  // Even though it makes no arithmetic difference as sizeof(char) is 1,
  // multiply the size of the object by the _sum_
  // char* buffer = malloc(sizeof(char) * (size + 1));
  
  // Even better as, size by the referenced object, not type 
  char* buffer = malloc(sizeof buffer[0] * (size + 1));

  // Add allocation check
  if (buffer == NULL) {
    // add error message here
    return EXIT_FAILURE;
  }

使用正确的return类型,添加结果检查

  // unsigned int result = fread(buffer, sizeof(char), size, file);
  // fread returns a size_t
  // size_t result = fread(buffer, sizeof(char), size, file);
  // Even better as
  size_t result = fread(buffer, sizeof buffer[0], size, file);
   
  if (result != size) {
    // add error message here
    return EXIT_FAILURE;
  }
      
  fclose(file); // OK

我们终于解决了 OP 最紧迫的问题

代码尝试使用 puts(buffer) 打印,它期望 buffer 是一个 字符串 。字符数组 buffer[] 不一定是字符串,因为它可能缺少 空字符 .

多种解决方案:追加一个空字符.

  // Make certain `buffer[]` contains a final '[=15=]'.
  buffer[result] = '[=15=]';
  // then print 
  puts(buffer);

或使用 fwrite() 打印,不需要 buffer[] 作为 字符串

  fwrite(buffer, sizeof buffer[0], result, stdout);

更深

剩余问题:

  • 使用 "rb" 读取数据并使用 stdout 写入(使用 puts()fwrite()。这会在某些平台上产生问题,如写入在 text_modestdout 典型默认模式)中,二进制数据 "\r\n" 可能打印为 "\r\r\n"。解决方法:重新打开stdout 二进制模式。

  • 读取的数据可能包含 空字符,在这种情况下,puts() 将在第一个 '[=32=]' 处提前停止 - 而不是附加的一。 fwrite() 避免了这个问题。

  • 文件大小可能超过 LONG_MAXSIZE_MAX 或超过 malloc() 可用内存。我们需要另一种方式来确定文件大小,并进行不同的分配以应对如此巨大的文件。

  • 在不常见的系统上,fseek(), ftell() 技巧不起作用。最好的解决方案是不需要将整个文件读入一个 array/string,而是读入多个块。将详细信息留到另一天。