【问题标题】:User-made fgets() vs standard library fgets()用户制作的 fgets() 与标准库 fgets()
【发布时间】:2018-08-04 08:44:20
【问题描述】:

标准库函数fgets()有两个缺点:

  • 函数的第二个参数类型为int
  • 它在提供的缓冲区中留下一个尾随换行符

我做了一个类似于fgets() 的简单函数,排除了上述缺点,以尝试提高我的一个程序的效率,该程序获取文本文件的行并在新的位置终止char 数组-line 字符使用函数strcspn()

但它真的更有效吗?为什么标准库函数比以下幼稚的实现具有优势?

#include <stdio.h>


char *my_fgets(char *buf, size_t maxCount, FILE *stream);


int main(int argc, char **argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s [filename]", argv[0]);
    }

    FILE *fp;
    fp = fopen(argv[1], "r");

    if (!fp)
    {
        perror(argv[1]);
        return 1;
    }

    char buf[256];
    /*while (fgets(buf, sizeof(buf), fp))
    {
        buf[strcspn(buf, "\n")] = '\0';
        // . . .
        puts(buf);
    }*/

    while (my_fgets(buf, sizeof(buf) - 1, fp))
    {
        puts(buf);
    }

    return 0;
}


char *my_fgets(char *buf,
    size_t maxCount, FILE *stream)
{
    int ch;
    size_t n = 0;

    while ((ch = fgetc(stream)) != EOF)
    {
        if (ch == '\n' || n == maxCount)
        {
            break;
        }
        else
        {
            buf[n++] = ch;
        }
    }
    if (n == 0 && ch == EOF)
    {
        return NULL;
    }
    buf[n] = '\0';
    return buf;
}

【问题讨论】:

  • 这个问题似乎更适合Code Review
  • 我认为您的问题would be on topic there:“以下领域的开放式反馈:最佳实践和设计模式使用、安全问题、性能、意外情况下的正确性”
  • 它可能效率较低的一个原因是标准库版本可能使用较低级别的操作系统特定 I/O 函数,而不是使用fgetc(),并且可以使用手工制作的汇编程序来实现针对目标系统的性能进行了调整。该标准仅指定了fgets() 的净效果——它不要求使用fgetc() 实现fgets(),甚至不需要用C 编写。
  • 您的函数很容易出错:它将maxCount + 1 字节写入缓冲区,这与fgets 不同。如果达到maxCount 限制,它会丢弃字符:如果流包含hello 并且您调用my_fgets(buf, 1, fp),它会将"h" 存储在buf 中并丢弃'e',将llo 留在流中。并且无法区分完整的行(到达实际的行尾 (\n))和部分行(my_fgets 停止,因为读取了 maxCount 字符)。
  • @machine_1 - 是的,您要么有一个尾随 '\n',要么您已读取指定的最大字符数,但缓冲区末尾没有 '\n'

标签: c performance function fgets standard-library


【解决方案1】:

性能

您使用fgetc() 逐个获取字符保证my_fgets() 无法与经过合理优化的解决方案竞争。为了获得良好的性能,您需要通过read() 系统调用将一些字符读入缓冲区,然后直接从该缓冲区使用数据。对每个字节执行完整的函数调用是很多的开销。

安全

fgets() 对于一般用途不安全:它会分割长行。这种情况通常是出乎意料的(例如,您的示例代码也不处理这种情况),并且当向您的程序提供很长的输入行时,通常会导致错误的行为。尾随\n 的存在实际上可以帮助您处理该问题。 如果你想要安全的软件,你必须在使用fgets()时明确处理缓冲区不足的情况。如果不这样做,你肯定有一个等待攻击的错误。您的实现甚至没有尝试解决这个问题。

这就是为什么我强烈建议使用可以处理任何长度的输入行的输入函数。在getline() 的 POSIX 兼容系统上:此函数为您分配足够大的缓冲区,因此您唯一的限制是可用 RAM。如果getline() 在您的平台上不可用,我建议重新实现其功能,而不是“更好”的fgets() 版本。

【讨论】:

    【解决方案2】:

    真的更高效吗?

    没有 - 没有更多在性能上有效。 my_fgets() 充其量可以有类似的效率,但即使这样也不太可能,因为 fgets() 可以访问精心制作的汇编代码,而 my_fgets() 不能 @Peter

    是的,如果功能目标是获取一行用户输入和not save the '\n',它可能具有编码效率,因为这比fgets() ... code_to_rid_\n() 更少编码


    但效率次于功能。

    my_fgets()input errors 有小问题。

    my_fgets() 可以丢弃字符,不像fgets() @melpomene

    size_t maxCount 的作用与 fgets() 的类似参数相差 1。


    作为标准库fgets() 的良好尝试有各种弱点,迫使编码人员尝试更好的低级输入行函数。

    【讨论】:

    • 修复了小错误,如果您想检查一下。 ideone.com/LXdyZh,顺便说一下,fgets() 不返回 NULL 如果 size 1 被传递,这可能会导致 while 循环无限循环。
    • @machine_1 代码审查的好地方是CR。请参阅 How to get the best value out of Code Review fgets() 通常不应在传递 1 时返回 NULL。使用fgets()n &lt;2,结果为troublesome
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-07
    相关资源
    最近更新 更多