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
.
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
.
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 thanRSIZE_MAX
. A new-line character, end-of-file, or read error shall occur within readingn-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 byn
from the stream pointed to by stdin, into the array pointed to bys
. 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;
}
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.
As others have pointed, gets_s()
is:
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;
}
}