Is there any gcc compiler option or something similar to bypass the obsolesence of the gets function?

Viewed 155

Let's get this out of the way first: I am fully aware of why the gets function is deprecated and how it can be used maliciously in uncontrolled environments. I am positive the function can be used safely in my setup.

"Alternative" functions won't do for my use case. The software I am writing is very performance-sensitive, so using fgets and then using one full string loop (either explicitely or by a helper function like strlen) to get rid of the newline at the end of the string simply won't do. Writing my own gets function using getchar or something similar will also probably be much less efficient than a native implementation (not to mention very error prone).

The Visual Studio compiler has a gets_s function which fits my needs, however this function is nowhere to be found in gcc. Is there any compiler option/flag to get this function back and/or some alternative function that it implements?

Thank you in advance.

3 Answers

Implementing your own safe gets() function using getchar_unlocked() is easy and reasonably efficient.

If your application is so performance sensitive, that you think fgets() and removing the scan is going to be the bottleneck, you should probably not use the stream functions at all and use lower level read() system calls or memory mapped files.

In any case, you should carefully benchmark your application and use profiling tools to determine where the time is spent.

Here is a simple implementation that returns the line length but truncates the line to whatever fits in the destination array buf of length n and returns EOF at end of file:

int my_gets(char *buf, size_t n) {
    int c;
    size_t i = 0;
    while ((c = getchar_unlocked()) != EOF && c != '\n') {
        if (i < n) {
            buf[i] = c;
        }
        i++;
    }
    if (i < n) {
        buf[i] = '\0';
    } else
    if (n > 0) {
        buf[n - 1] = '\0';
    }
    if (c == EOF && i == 0) {
        return EOF;
    } else {
        return (int)i;
    }
}

If your goal is to parse a log file line by line and only this function to read from stdin, you can implement a custom buffering scheme with read or fread in a custom version of gets(). This would be portable and fast but not thread safe nor elegant.

Here is an example that is 20% faster than fgets() on my system:

/* read a line from stdin
   strip extra characters and the newline
   return the number of characters before the newline, possibly >= n
   return EOF at end of file
 */
static char gets_buffer[65536];
static size_t gets_pos, gets_end;

int my_fast_gets(char *buf, size_t n) {
    size_t pos = 0;
    for (;;) {
        char *p = gets_buffer + gets_pos;
        size_t len = gets_end - gets_pos;
        char *q = memchr(p, '\n', len);
        if (q != NULL) {
            len = q - p;
        }
        if (pos + len < n) {
            memcpy(buf + pos, p, len);
            buf[pos + len] = '\0';
        } else
        if (pos < n) {
            memcpy(buf + pos, p, n - pos - 1);
            buf[n - 1] = '\0';
        }
        pos += len;
        gets_pos += len;
        if (q != NULL) {
            gets_pos += 1;
            return (int)pos;
        }
        gets_pos = 0;
        gets_end = fread(gets_buffer, 1, sizeof gets_buffer, stdin);
        if (gets_end == 0) {
            return pos == 0 ? EOF : (int)pos;
        }
    }
}

I suppose you are running on Windows, so it's possible that none of this information is relevant. The tests below were done on a Linux Ubuntu laptop, not particularly leading edge. But, for what it's worth:

  1. If gets is in your standard library (it's in my standard library, fwiw), then you only need to declare it to use it. It doesn't matter that it has been removed from your standard library headers:

    char* gets(char* buf);
    

    You could declare gets_s yourself, too, if that's the one you want to use.

    This is entirely legal according to the C standard: "Provided that a library function can be declared without reference to any type defined in a header, it is also permissible to declare the function and use it without including its associated header." (§7.1.4 ¶2)

    See @dbush's answer for the linker option to avoid the deprecation message.

  2. That's not actually what I would do. I'd use the Posix-standard getline function. On a standard Linux install, you need a feature-test macro to see the declaration: #define _POSIX_C_SOURCE 200809L or #define _XOPEN_SOURCE 700. (You can use larger numbers if you want to.) getline avoids many of the issues with fgets (and, of course, gets) because it returns the length of the line read rather than copying its buffer argument to the return value. If your input handler needs (or can use) this information, it might save a few cycles to have it available. It certainly can be used to check for and remove the newline.

    On my little laptop, using a file of 100,000,000 words as you suggest, I got the following timings for the three alternatives I tested:

    gets (dangerous)     fgets (+strlen)      getline
    ----------------     ---------------      -------
         1.9958               2.3585           2.0350
    

    So it does show some overhead with respect to gets, but it's not (IMHO) very significant, and it's quite possible that the fact that you don't need strlen in your handler will recoup the small additional overhead.

Here are the loops I tested. (I call an external function called handle to avoid optimisation issues; in my test, all handle does is increment a global line count.)

gets (dangerous)

  char buf[80]; // Disaster in waiting
  for (;;) {
    char* in = gets(buf);
    if (in == NULL) break;
    handle(in);
  }

fgets (+strlen)

  char buf[80]; // Safe. But long lines are not handled correctly.
  for (;;) {
    char* in = fgets(buf, sizeof buf, stdin);
    if (in == NULL) break;
    size_t inlen = strlen(in);
    if (inlen && in[inlen - 1] == '\n')
      in[inlen - 1] = 0;
    handle(in);
  }

getline

  size_t buflen = 80;        // If I guessed wrong, the only cost is an extra malloc.
  char* in = malloc(buflen); // TODO: check for NULL return.
  for (;;) {
    ssize_t inlen = getline(&in, &buflen, stdin);
    if (inlen == -1) break;
    if (inlen && in[inlen - 1] == '\n')
      in[inlen - 1] = 0;
    handle(in);
  }
  free(in);

You can use the -Wno-deprecated-declarations option to prevent warnings for all deprecated functions. If you want to to disable it in specific instances, you can use pragmas for this:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
gets(str);
#pragma GCC diagnostic pop

Note that in both cases this only prevents the compiler from complaining. The linker will still give a warning.

Related