【问题标题】:C question about allocating memory for a char* to be used with fread关于为要与 fread 一起使用的 char* 分配内存的 C 问题
【发布时间】:2020-07-15 03:09:44
【问题描述】:

所以我有这个代码

FILE* file = fopen("file.txt", "r");
if(file == NULL)
{
    printf("Failed to open file.\n");
    return NULL;
}

fseek(file, 0L, SEEK_END);
long bufferSize = ftell(file);
fseek(file, 0L, SEEK_SET);

char* buffer = (char*) malloc(bufferSize);
if(buffer == NULL)
{
    printf("Failed to allocate memory for buffer.\n");
    return NULL;
}

fread(buffer, sizeof(char), bufferSize, file);
fclose(file);

当使用 printf("%s", buffer) 打印到控制台时,这似乎工作得非常好,但我想知道这是否应该导致缓冲区溢出或者它是否错误,因为最后似乎没有空终止符. 让我们假设 file.txt 中正好有 4 个字符。当 bufferSize 被计算出来时,它的值为 4终止符。我到处都看到人们阅读整个文本文件的例子,他们使用这样的代码,但这不应该是用文件中的字符创建一个 char* 而没有结束空终止符吗?我应该使用 malloc(bufferSize + 1) 分配这个缓冲区并添加一个空终止符吗?

【问题讨论】:

  • 正如您所想的那样,仅使用printf 并不是一个好主意,因为您根本不知道\0 是否被读取。所以我会分配bufferSize+1字节并手动设置buffer[bufferSize] = 0,然后你就可以放心使用printf了。
  • OT: about: printf("Failed to open file.\n"); 错误信息应该输出到stderr,而不是stdout。当错误指示来自 C 库函数时,还应输出系统认为发生错误的文本原因。建议:perror( "Failed to open file." );

标签: c


【解决方案1】:

当使用 printf("%s", buffer) 打印到控制台时,这似乎工作得很好

看似完美无缺是undefined behavior的完美体现。

我应该使用 malloc(bufferSize + 1) 分配这个缓冲区并添加一个空终止符吗?

如果您希望将%s printf 格式说明符与指向可打印字符的连续字节的指针一起使用,则这些字节需要以零字节终止。或者另一种方式,%sprintf 格式说明符需要一个以零结尾的字节序列。否则,会发生未定义的行为。

所以:

  • 您的输入文件包含一个零字节,因此%s 停止在那里输出。
  • 您需要自己提供一个零终止字节,以确保%s 知道在哪里停止。
  • 或者您可以自己迭代字节for (...) { printf("%c", buffer[i]); } 或(假设bufferSize 低于INT_MAX,所以可能是这样)只需通过指定格式说明符的精度来告诉printf 何时停止,例如: printf("%.*s", (int)bufferSize, buffer);

否则会发生未定义的行为。

【讨论】:

  • 为那个空终止符分配 1 个额外字节但使用 calloc() 将其全部初始化为 0 怎么样?
  • 不完全一样。它的效率有点低,因为它正在初始化无论如何都可能要写入的字节。然后是因为你不需要n[sz-1] = '\0';,所以它将行数减少了1。最后一部分是我的最爱,因为它使代码更具可读性,并且作为一种实践使代码更安全。恕我直言
【解决方案2】:

根据您分配的缓冲区大小和操作系统提供的分配单元的大小,分配结束时通常会有额外的字节。这意味着根据您以后使用内存的方式,精确的缓冲区分配可能会导致失败,或者在分配结束时可能有备用字节,您的 fread() 不会覆盖。结果?您可以使用具有偶然大小的文件来测试您的程序,但程序在交付后可能会间歇性地失败。

快速修复?始终在缓冲区末尾分配更多空间 - 取决于程序如何解释字节(char、short、int、long、long long、struct)。

请注意,如果字符串嵌套在结构中,则分配单元的大小不太可能为您节省空间,其中结构元素紧靠在一起。但是奇数大小的字符串仍然有空闲空间,具体取决于编译器标志。

请注意,您的具体用法是查找文件末尾,并将整个文件放入内存中。您的操作系统可能以 16、32 或 64 字节块的形式提供内存。这意味着您有 1/16、1/32 或 1/64 的机会意外溜出分配的缓冲区。

建议: (0) 始终分配额外的填充物,以缓冲撞墙。 (1) 考虑使用 fstat() 而不是 ftell()? (2)考虑内存映射文件,而不是使用malloc/free和fread。

【讨论】:

    猜你喜欢
    • 2021-08-27
    • 2023-03-13
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    相关资源
    最近更新 更多