【问题标题】:Are there ways to overcome the constraints of fgets()?有没有办法克服 fgets() 的限制?
【发布时间】:2020-05-07 12:15:57
【问题描述】:

fgets() 函数有两个问题。第一个是,如果行的大小比传递的缓冲区长,则该行被截断。第二个是,如果从文件中读取的行嵌入了'\0' 字符,那么就无法知道该行的实际长度。我想替换fgets(),它动态地为读取的行分配空间并提供读取的行的大小。我已经编写了动态分配空间的代码。我无法弄清楚如何读取行的大小。我是初学者。非常感谢。

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

char *myfgets(FILE *fptr, int *size);

char *myfgets(FILE *fptr, int *size) {
    char *buffer;
    char *ret;

    buffer = (char *)malloc((*size) * sizeof(char));
    if (buffer == NULL)
        error(1, 0, "No memory available\n");
    ret = fgets(buffer, *size, fptr);
    if (ret == NULL)
        error(1, 0, "Error in reading the file\n");
    return ret;
}

int main(int argc, char *argv[]) {
    char *file;
    FILE *fptr;
    int size;
    char *result;

    if (argc != 3)
        error(1, 0, "Too many or few arguments <File_name>, <Number of bytes to read>\n");
    file = argv[1];
    size = atoi(argv[2]);
    fptr = fopen(file, "r");
    if (fptr == NULL)
        error(1, 0, "Error in opening the  file\n");
    result = myfgets(fptr, &size);
    printf("The line read is :%s", result);
    free(result);
    return 0;
}

【问题讨论】:

  • 如果你想处理像'\0'这样的非ASCII字符,那么你应该使用read(),而不是fgets()
  • 您不能期望以任何有意义的方式处理 c 类型字符串中的 \0
  • @r3mainer "... 非 ASCII 字符,如 '\0'" --> '\0'ASCII 字符 (ASCII NUL)。当'\n' 读取时,read() 不会停止读取。所以也不符合要求。
  • @Manikandan:您可以通过单击其分数下方的灰色复选标记来接受其中一个答案。

标签: c


【解决方案1】:

使用getline(3) 读取未知长度的完整行。它会根据需要分配内存来保存所有内容。

该函数也可以处理正在读取的行中的 0 字节。从链接的手册页(强调添加):

成功时,getline() 和 getdelim() 返回读取的字符数,包括分隔符,但不包括终止空字节 ('\0')。 此值可用于处理读取的行中嵌入的空字节。

所以你只需要保存它的返回值而不是使用strlen()

【讨论】:

    【解决方案2】:

    您已正确识别 fgets() 中的 2 个问题,但您提出的替代方案并未解决其中任何一个问题,因为您仍然调用 fgets()

    您应该编写一个循环,重复调用getc(),直到获得EOF'\n',然后您将读取的字节存储到分配的数组中,并根据需要重新分配。

    这是一个简单的版本:

    // Read a full line from `fptr`
    // - return `NULL` at end of file or upon read error like `fgets()`.
    // - otherwise return a pointer to an allocated array containing the
    //   characters read, up to and including the newline and a null terminator.
    // - store the number of bytes read into *plength.
    // - the buffer is null terminated, and it may contain embedded null bytes
    //   if such bytes were read from the file
    char *myfgets(FILE *fptr, size_t *plength) {
        size_t length = 0;
        char *buffer = NULL, *newp;
        int c;
    
        for (;;) {
            if (c = getc()) == EOF) {
                if (!feof(fptr)) {
                    /* read error: discard data read so far and return NULL */
                    free(buffer);
                    buffer = NULL;
                    length = 0;
                }
                break;
            }
            if ((newp = realloc(buffer, length + 2)) == NULL) {
                free(buffer);
                error(1, 0, "Out of memory for realloc\n");
                return NULL;
            }
            buffer = newp;
            buffer[length] = c;
            length++;
            if (c == '\n')
                break;
        }
        if (length != 0) {
            buffer[length] = '\0';
        }
        *plength = length;
        return buffer;
    }
    

    【讨论】:

    • 不错。请注意,当 ferror(stdin) 为真时,fgets() 返回 NULL,因此需要做更多工作来处理罕见的极端情况。
    • @chux-ReinstateMonica:确实fgets() 在文件末尾或在读取错误的情况下返回NULL。上述函数的作用与getc() 相同,在相同情况下将返回EOF。如果没有字符存储到目标数组中,则返回 NULL,就像 fgets() 一样。
    • @chux-ReinstateMonica:实际上,读取错误的行为是不同的,即使字节已经存储在数组中,也应该返回NULL。答案已更新。 (我也应该更新我自己的 C 库:)
    • 更好。注意:在深入研究here 时,if (!feof(fptr))if (ferror(fptr)) 更有优势,因为当设置了错误和文件结束标志时,它是一个更好的测试。
    【解决方案3】:

    “固定”fgets() 的各种方法:

    1) 按照@Shawn 的建议使用非C 库标准getline()。通常在 *nix 和源代码中可用,很容易找到。不幸的是,它需要一个新类型:ssize_t

    2) 滚动您自己的getc() 代码@chqrlie。角落案例可能很棘手。

    3) 根据需要反复拨打fgets()。用'\n' 预先填充缓冲区并查找'\n' 的第一次出现、它的位置、下一个字符以帮助确定长度。 (只有少数情况需要考虑)

    4) 根据需要反复调用scanf("%99[^\n]%n", buf100, &amp;n)getc() 获取'\n'。查看返回值和n 来确定长度。

    5) 可能的其他人

    对设计的一个很好的功能测试是它报告案例的效果如何:

    • 快乐的路径:一行被读取,内存分配,没有问题。

    • 文件结束:由于文件结束,没有读取任何内容。

    • 内存不足。

    • 出现输入错误。

    其他注意事项:

    • 你真的要保存'\n'吗?

    • 性能。


    对于我来说,“动态分配空间”没有限制,代码引入了恶意用户通过输入病态的长行来压倒内存资源的能力。我建议将输入限制在合理的范围内,而不是赋予用户这种能力。过长的输入是一种应该被检测到而不是启用的攻击。

    所以我会开始

    char *myfgets(FILE *fptr, size_t limit, size_t *size) {
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-12
      • 2021-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      相关资源
      最近更新 更多