4

I know fgets() is a more common and widespread option for string input, but C11 has been around for 9 years. Why is gets_s() still out of work?

Even when I add -std=c11, it still doesn't work, even though gets_s() should be in stdio.h.

Jorengarenar
  • 2,705
  • 5
  • 23
  • 60
brushmonk
  • 109
  • 7
  • 1
    `gets_s` is optional in C11. There's no requirement for any compiler to implement that and AFAIK `gcc` does not. – kaylum Jul 19 '20 at 04:52
  • 1
    Just use `fgets()`, which unlike `gets_s()`, is actually portable outside of MSVC. – Shawn Jul 19 '20 at 04:56
  • 1
    Note that gcc is just a compiler and doesn't implement any stdio functions at all. So your question is why your system's standard C library doesn't implement it, but you haven't said what system that is. Anyway, gcc and its authors are not to blame. – Nate Eldredge Jul 20 '20 at 13:33
  • 1
    As far as I know, **no one** implements all the Annex K functions as standardized. Not even Microsoft, who was the principal behind their introduction. Microsoft's version is similar, but not identical. I also don't know many people outside Microsoft's influence who think these optional functions provide much genuine advantage, and few, if any, C implementations other than Microsoft's implement them at all. – John Bollinger Jul 20 '20 at 13:34
  • The standard itself recommends fgets over gets_s. – Lundin Aug 03 '20 at 12:07

3 Answers3

4

Because it's optional. And the persons behind gcc seems to think it is a bad idea to include it. I don't know how they reasoned, but hints can be found in the C standard:

Recommended practice

The fgets function allows properly-written programs to safely process input lines too long to store in the result array. In general this requires that callers of fgets pay attention to the presence or absence of a new-line character in the result array. Consider using fgets (along with any needed processing based on new-line characters) instead of gets_s.

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

If you want to use gets_s, then use another compiler. Or write your own wrapper, but don't call it gets_s because it's quite tricky to get it completely identical to the specs.

The C standard says this:

Runtime-constraints

s shall not be a null pointer. n shall neither be equal to zero nor be greater than RSIZE_MAX. A new-line character, end-of-file, or read error shall occur within reading n-1 characters from stdin.

If there is a runtime-constraint violation, s[0] is set to the null character, and characters are read and discarded from stdin until a new-line character is read, or end-of-file or a read error occurs.

Description

The gets_s function reads at most one less than the number of characters specified by n from the stream pointed to by stdin, into the array pointed to by s. No additional characters are read after a new-line character (which is discarded) or after end-of-file. The discarded new-line character does not count towards number of characters read. A null character is written immediately after the last character read into the array.

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, and the other elements of s take unspecified values.

There is one thing here that does not make sense at all. A runtime constraint is that s should not be a null pointer. On runtime constraint violoations, s[0] should be set to zero. But the operation s[0] = '\0' has undefined behavior if s is a null pointer.

Here is my take on trying to implement it, but IMO the specs are a mess, and I would not trust this. It was tricky to get it right.

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;
}
klutt
  • 30,332
  • 17
  • 55
  • 95
  • Variable `c` is undeclared. Souldn't it be `ch` instead and `int ch;` outside loop? – Jorengarenar Jul 19 '20 at 06:01
  • @Jorengarenar I just realized it was trickier than I thought so I removed it :D – klutt Jul 19 '20 at 06:04
  • Yeah, I've just read the standard and no wonder compilers don't bother with it – Jorengarenar Jul 19 '20 at 06:09
  • @Jorengarenar I think I got it right now, but yeah, those specs were ... interesting... – klutt Jul 19 '20 at 06:25
  • @Jorengarenar: I find it rather bizarre that the C Standard has added new ways of handling console input, but has yet to provide any support for interactive applications that's better than building one's own routines based upon `getchar()`. I suppose people have been building functions based on `getchar()` for so long there's little need for the Standard to provide nice built-in functions, but the language would be a lot nicer for newcomers if there were some decent console I/O support. – supercat Jul 22 '20 at 18:43
  • @supercat Late response, but I suspect that it at least partially is due to that C aims to work on such a huge range of computers. AFIK, the standard does not assume that you have neither a keyboard nor a monitor. It only assumes that there exist an input and an output stream. – klutt Oct 13 '20 at 17:02
  • @klutt: Any implementation should be able to support a function that would indicate what kinds of interactive I/O it supports. While interactive consoles aren't universal, portability would be enhanced far more by providing a standard means of utilizing common features on platforms that support them, than by having the Standard exclude from its consideration everything that isn't universally supportable. – supercat Oct 13 '20 at 17:12
  • @supercat Well, I don't know. After all, C is not really the language of choice when it comes to applications with heavy user interaction. And I don't think "being beginner friendly" is a very strong argument to extend the standard. IMHO, the biggest problem is that beginners classes relies to much on writing programs requiring user input. – klutt Oct 13 '20 at 17:22
  • @klutt: In what sense would C not be the language of choice for writing a program like `more`, or even something like `vi` on a system which is expected to be used with a known variety of serial-connected terminals? If one wants to write a text editor that will work with a serial-connected VT100, one shouldn't need to know any platform-specific details of what sits between the program and the terminal. – supercat Oct 13 '20 at 17:27
  • @supercat I don't deny that you have a point. I'm not just very convinced how important it is. – klutt Oct 13 '20 at 17:34
  • @klutt: On many systems, the primary purpose of most programs is to interact with a user via terminal interface. While there are some systems where many programs would need something beyond a basic terminal interface, support for basic terminal I/O would have massively increased the range of practical applications that could be written to run on many platforms interchangeably from a small minority to a vast majority. – supercat Oct 13 '20 at 18:04
2

While it would be useful to have an alternative to fgets() which will always read an entire line, discarding excess information if need be, and report how many characters were read, gets_s is not such a function. The gets_s function would only be appropriate in scenarios in which any over-length input lines should be completely discarded. The only good ways of performing line-based I/O are either to build one's own line-input routine based upon fgetc() or getchar(), use fgets() with corner-case logic that's as big as a character-based get-line routine, or--if one wants to maximize performance and the stream doesn't have to be shared with anything else--use fread() and memchr(), persisting read data in a private buffer between calls to the get-line routine.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

As others have pointed, gets_s() is:

  1. optional (and many compilers actually don't implement it)
  2. since C11 (so previous standards definitely don't have it)

If you really need to have something instead of fgets(), then you can implement wrapper yourself, e.g.:

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;
    }
}
Jorengarenar
  • 2,705
  • 5
  • 23
  • 60
  • I would strongly advice against that wrapper. You should call it something else, because it's not completely compatible with the specs of `gets_s`. – klutt Jul 19 '20 at 05:20
  • Good. Actually, it's quite likely that it would have caused very different behavior. – klutt Jul 19 '20 at 05:23
  • Also, you should change the type of count to size_t – klutt Jul 19 '20 at 05:26
  • @klutt `fgets()` is `fgets(char* str, int count, FILE* stream)`, that's why `int` – Jorengarenar Jul 19 '20 at 05:27
  • @klutt Out of curiosity, what would be so different from `gets_s()`? – Jorengarenar Jul 19 '20 at 05:27
  • Hmmm, yes that's correct. Well, I guess I would have rewritten it with `fgetc` instead then, because `size_t` makes more sense and is more similar to `gets_s(char *, rsize_t)`. But a differences is that this version would likely modify one more character of `str`. That would typically not matter, but it is a difference. Also, `gets_s` flushes stdin until next EOL on failure. – klutt Jul 19 '20 at 05:32
  • Right, the place for '\n'. Well, it's just an example anyway – Jorengarenar Jul 19 '20 at 05:39
  • I made a version that you can look at if you want – klutt Jul 19 '20 at 05:57
  • @Jorengarenar: Among other things, the specification for `gets_s` specifies that overly long input lines are consumed from the input stream but discarded completely. – supercat Aug 03 '20 at 22:09