【问题标题】:Why `gets_s()` still isn't implemented in GCC (9.3.0)?为什么 `gets_s()` 仍未在 GCC (9.3.0) 中实现?
【发布时间】:2020-11-08 14:01:03
【问题描述】:

我知道fgets() 是一个更常见和广泛的字符串输入选项,但 C11 已经存在 9 年了。为什么gets_s() 还没有工作?

即使我添加了-std=c11,它仍然不起作用,即使gets_s() 应该在stdio.h 中。

【问题讨论】:

  • gets_s 在 C11 中是可选的。任何编译器都不需要实现它,AFAIK gcc 不需要。
  • 只需使用 fgets(),它与 gets_s() 不同,实际上可以在 MSVC 之外移植。
  • 请注意,gcc 只是一个编译器,根本不实现任何 stdio 函数。所以你的问题是为什么你的系统的标准 C 库没有实现它,但你没有说那是什么系统。无论如何,gcc 及其作者不应该受到责备。
  • 据我所知,没有人按照标准实现了所有的附件 K 功能。甚至微软,谁是他们介绍背后的负责人。微软的版本类似,但不完全相同。我也不知道有多少人认为这些可选功能提供了很多真正的优势,而且除了 Microsoft 的 C 实现之外,几乎没有其他人认为这些可选功能提供了很多真正的优势。
  • 标准本身推荐 fgets 而不是 gets_s。

标签: c gcc c11 gets


【解决方案1】:

虽然有一个替代fgets() 的方法会很有用,它总是会读取整行,如果需要会丢弃多余的信息,并报告读取了多少个字符,gets_s 不是这样的功能。 gets_s 函数仅适用于应完全丢弃任何超长输入行的情况。执行基于行的 I/O 的唯一好方法是基于 fgetc()getchar() 构建自己的行输入例程,将 fgets() 与基于字符的 get 一样大的极端情况逻辑使用-line 例程,或者 - 如果想要最大化性能并且流不必与其他任何东西共享 - 使用 fread()memchr(),在调用 get 之间将读取数据持久保存在私有缓冲区中 -线路例程。

【讨论】:

    【解决方案2】:

    因为它是可选的。 gcc 背后的人似乎认为包含它是一个坏主意。我不知道他们是如何推理的,但可以在 C 标准中找到提示:

    推荐做法

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

    https://port70.net/~nsz/c/c11/n1570.html#K.3.5.4.1

    如果您想使用gets_s,请使用其他编译器。或者编写自己的包装器,但不要将其称为 gets_s,因为要使其与规范完全一致非常棘手。

    C 标准是这样说的:

    运行时约束

    s 不得为空指针。 n 既不能等于 0,也不能大于 RSIZE_MAX。从标准输入读取n-1 字符时会出现换行符、文件结尾或读取错误。

    如果存在运行时约束违规,则将 s[0] 设置为空字符,并从标准输入读取并丢弃字符,直到读取换行符、文件结尾或读取错误发生。

    说明

    gets_s 函数最多从 stdin 指向的流中读取比n 指定的字符数少一个到s 指向的数组。在换行符(被丢弃)或文件结尾之后不会读取其他字符。丢弃的换行符不计入读取的字符数。在读入数组的最后一个字符之后立即写入一个空字符。

    如果遇到文件尾并且没有字符被读入数组,或者在操作过程中发生读取错误,则将s[0]设置为空字符,s的其他元素取未指定价值观。

    这里有一件事根本没有意义。运行时约束是 s 不应该是空指针。在运行时约束违反时,s[0] 应设置为零。但是如果s 是空指针,则操作s[0] = '\0' 具有未定义的行为。

    这是我尝试实现它的看法,但 IMO 的规格一团糟,我不会相信这一点。把它做好是很棘手的。

    char *my_gets_s(char *s, size_t n)
    {
        if(!s) return NULL;
    
        size_t i=0;
        int ch;
    
        for(i=0; i<n-1; i++) {
            ch = fgetc(stdin);
    
            // If end-of-file is encountered and no characters have been read into the array,                          
            // or if a read error occurs during the operation, then s[0] is set to the null character                  
            if(ferror(stdin) || (ch == EOF && i == 0)) {
                s[0] = '\0';
                return NULL;
            }
    
            // If EOF and we have read at least one character                                                          
            if(ch == EOF) {
                s[0] = '\0';
                return s;
            }
    
            s[i] = ch;
    
            if(ch == '\n') {
                s[i] = '\0';
                return s;
            }
        }
    
        while ((ch = getchar()) != '\n' && ch != EOF);
        s[0] = '\0';
        return NULL;
    }
    

    【讨论】:

    • 变量c 未声明。难道不是ch 而不是int ch; 外循环吗?
    • @Jorengarenar 我刚刚意识到它比我想象的要棘手,所以我删除了它:D
    • 是的,我刚刚阅读了标准,难怪编译器不理会它
    • @Jorengarenar 我想我现在明白了,但是,是的,那些规格……很有趣……
    • @Jorengarenar:我觉得很奇怪,C 标准添加了处理控制台输入的新方法,但还没有为交互式应用程序提供任何支持,这比基于 @987654341 构建自己的例程更好@。我想人们一直在构建基于getchar() 的函数,因此标准几乎不需要提供好的内置函数,但是如果有一些不错的控制台 I/O 支持,该语言对于新手来说会更好。
    【解决方案3】:

    正如其他人所指出的,gets_s() 是:

    1. 可选(许多编译器实际上并没有实现它)
    2. 从 C11 开始(所以以前的标准肯定没有)

    如果你真的需要一些东西而不是fgets(),那么你可以自己实现包装器,例如:

    char* myGets(char* str, int count)
    {
        if (fgets(str, count, stdin)) {
            for (int i = 0; i < count; ++i) {
                if (str[i] == '\n') {
                    str[i] = '\0';
                    break;
                }
            }
            return str;
        } else {
            return NULL;
        }
    }
    

    【讨论】:

    • 我强烈建议不要使用该包装器。你应该叫它别的名字,因为它与gets_s的规范不完全兼容。
    • 好。实际上,它很可能会导致非常不同的行为。
    • 另外,您应该将计数类型更改为 size_t
    • @klutt fgets()fgets(char* str, int count, FILE* stream),这就是为什么 int
    • @klutt 出于好奇,与gets_s() 有何不同?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 2012-06-02
    • 2014-01-22
    相关资源
    最近更新 更多