【问题标题】:Since the Standard C committee did not standardize a simple replacement for gets(), what should it be?由于标准 C 委员会没有标准化 get() 的简单替换,它应该是什么?
【发布时间】:2016-03-06 00:12:46
【问题描述】:

gets 函数首先在 C99 中被弃用,最后在 C11 中被删除。然而,在 C 库中没有直接替代它。

fgets() 不是直接替换,因为它不会删除最终的'\n',文件末尾可能不存在该'\n'。许多程序员也弄错了。

有一个删除换行符的单行代码:buf[strcspn(buf, "\n")] = '\0';,但这并不重要,并且经常需要解释。它也可能效率低下。

这会适得其反。许多初学者仍然使用gets(),因为他们的老师很蹩脚,或者他们的教程已经过时了。

Microsoft 提出了gets_s() 和许多相关函数,但它并没有默默地截断过长的行,这种违反约束的行为并不完全简单。

BSD 和 GNU libc 都有 getline,在 POSIX 中标准化,通过 realloc 分配或重新分配缓冲区...

教初学者了解这个烂摊子的最佳方法是什么?

【问题讨论】:

  • POSIX getlinefgets 更重要。 scanf%[ 是另一种选择,尽管它有自己的缺陷。 fgets 对我来说似乎并不算太​​糟糕,它的优点是能够判断线路是否超出缓冲区。
  • @M.M 使用puts,人们会问“但是gets 有什么不好呢?”请记住,这个问题本质上是教学,而不是技术
  • 这将是解释 gets 到底有什么不好的绝佳机会。初学者 C 教育必须包括对缓冲区溢出的讨论以及不这样做的重要性。
  • @M.M: scanf 绝对不是gets() 的合适替代品。大小限制参数减一并且必须在格式字符串中明确指定,不雅到它的最大值! scanf_s 稍好一些,但在 BSD 和 Linux 中不受支持,例如 gets_s...
  • 好的,我相信委员会成员有时会阅读 stackoverflow,如果他们不阅读,则可能有证据表明他们为什么没有提供直接替代品或推荐的替代品。我也在问如何最好地教初学者这方面的知识。

标签: c language-lawyer glibc libc


【解决方案1】:

这个问题的性质是,会有猜测和意见。但我们可以从 C99 基本原理和 C11 标准中找到一些信息。

C99 rationale,当 gets() 被弃用时,说明了弃用它的以下原因:

因为gets不检查缓冲区溢出,一般来说是不安全的 在其输入不受程序员控制时使用。这有 引起一些人质疑它是否应该出现在标准中 全部。委员会认为gets 是有用和方便的 当程序员确实有足够的能力时的那些特殊情况 控制输入​​,并且作为长期存在的实践,它 需要一个标准规范。但一般来说,首选 函数是 fgets(参见 §7.19.7.2)。

我也不认为gets_s() 可以被视为替代方案。因为gets_s()是一个可选接口。 C11 实际上推荐fgets() 而不是gets_s()

§K.3.5.4.1,C11 草案

fgets 函数允许正确编写的程序安全地处理 输入行太长而无法存储在结果数组中。一般来说这个 要求 fgets 的调用者注意存在或 结果数组中没有换行符。考虑使用 fgets(以及基于换行符的任何需要的处理) 而不是gets_s。

因此,fgets() 是 ISO C 中 gets() 的唯一真正替代品。fgets() 等效于 gets(),除非如果有缓冲区空间,它将在换行符中读取。那么是否值得引入一个新界面,该界面对长期使用且广泛使用的 (fgets()) 界面进行了微小的改进?国际海事组织,没有。

此外,许多现实世界的应用程序并不仅限于 ISO C。所以有机会使用扩展和 POSIX getline() 等作为替代品。

如果有必要在 ISO C 中找到编写解决方案,那么很容易为 fgets() 编写一个包装器,例如 my_fgets(),它会删除换行符(如果存在)。

当然,向新手教授fgets() 涉及解释潜在的换行问题。但是IMO,这并不难理解,打算学习C的人应该能够很快掌握它。它(查找最后一个字符,如果是字符“X”则替换它)甚至可以被认为是初学者的一个很好的练习。

因此,鉴于上述原因,我想说 ISO C 中的新功能并没有压倒性的必要性来真正替代 gets()

【讨论】:

    【解决方案2】:

    这个问题在很大程度上需要猜测,而不是从委员会会议记录或其他东西中引用,但作为一般原则,委员会 (WG14) 通常避免发明新接口,而是更喜欢记录和制定严格的现有实践(例如 snprintflong longinttypes.h 类型等),有时采用 C 之外的其他标准/接口定义(例如,来自 IEEE 浮点的复杂数学、来自 C++ 的原子模型等)。 gets 没有这样的替代品可以采用,可能是因为 fgets 通常被认为是优越的(当文件结束时没有换行符时它是无损的)。如果您真的想要直接替换,可以使用以下方法:

    char buf[100];
    scanf("%99[^\n]%*1[\n]", buf);
    

    当然使用起来很笨拙,尤其是在缓冲区大小可变的情况下。

    【讨论】:

    • 委员会 (WG14) 通常避免发明新接口 你在开玩笑吗?他们发明了一系列价值可疑的多字符界面。
    • 此代码因空行而失败(即流中的裸'\n'):如果没有字符匹配,%[匹配失败,因此不会继续到下一个说明符。
    • @M.M:在这种情况下,您还需要一个单独的操作来空终止(未写入的)buf
    • @chqrlie 值得指出 wchar_t 可以是 16 位,因为 Unicode 应该是 16 位。
    • @chqrlie:这实际上行不通。 mbrtowc 只能产生一个wchar_twcrtomb 只能处理一个。 uchar16_t 函数解除了此 API 限制,因此它们实际上可以支持 UTF-16,但 Windows 只是有缺陷,非 BMP 代码点根本无法与 C mb/wc API 一起使用。 (当然他们希望您忽略标准 API 并改用 WinAPI 函数...)
    【解决方案3】:

    IMO,任何替换都需要传递 sizechar * 目标,因此需要根据具体情况进行代码更改。一刀切被认为是不可能的,因为size 在到达gets() 的时间码经常丢失/未通过。鉴于我们有 12 年的警告(C99 到 C11),怀疑委员会认为该问题将在 2011 年消失。

    哈!

    标准 C 委员会应该进行替换,该替换也通过了目的地的大小。像下面这样。 (这可能存在名称冲突问题)

    char *gets_replacement(char *s, size_t size);
    

    我尝试了一个基于 fgets() 的替换,它利用了 VLA(C11 中的可选)

    char *my_gets(char *dest, size_t size) {
      // +2 one for \n and 1 to detect overrun
      char buf[size + 2];
    
      if (fgets(buf, sizeof buf, stdin) == NULL) {
        // improve error handling - see below comment
        if (size > 0) {
          *buf = '\0';
        }
        return NULL;
      }
      size_t len = strlen(buf);
      if (len > 0 && buf[len - 1] == '\n') {
        buf[--len] = '\0';
      }
    
      // If input would have overrun the original gets()
      if (len >= size) {
        // or call error handler
        if (size > 0) {
          *buf = '\0';
        }
        return NULL;  
      }
      return memcpy(dest, buf, len + 1);
    }
    

    【讨论】:

    • 鉴于我们有 12 年的警告(C99 到 C11),怀疑委员会认为问题将在 2011 年消失。 根据我们在 stackoverflow 上的经验,问题很粘。初学者还是用gets
    • 您提出的实现与fgets 有相同的缺点:buf 在读取错误或输入过长时是不确定的。我认为在这种情况下设置*buf = '\0'; 会更好,前提是size > 0fgets 在 EOF 时保持缓冲区不变,但这种指定的行为更容易出错而不是有用。
    • @chqrlie 同意。 “哈!”是为了表明我不同意这样的推理,虽然在 1999 年很有希望,但到 2011 年并没有发挥作用。
    • @chqrlie 同意你的想法。当fgets() 返回NULL 时,一个迂腐的解决方案会更深入地研究ferror()feof()。使用fgets(),缓冲区在feof() 上是单独存在的,但在ferror() 上是不确定的。我怀疑gets() 的工作方式相同。所以*buf = '\0' 可能只对ferror() 有意义。
    • @chqrlie 在第二种情况下,我使用了不可见的交换(代码行)例程。现已修复。
    猜你喜欢
    • 2011-06-07
    • 2013-04-15
    • 2010-09-19
    • 2010-11-19
    • 1970-01-01
    • 2021-04-26
    • 1970-01-01
    相关资源
    最近更新 更多