Reputation: 39
I have a college exercise to do, that consists in writing a bank in C and I do know that scanf
is a really buggy function and fgets
reads the '\n'
, that I don't really want to be read. So I have written new functions trying to solve this problem:
char *readstr(char *buff, size_t bytes) {
char *ptr;
for (ptr = buff;
(*ptr = getchar()) != '\n' && *ptr != EOF && ptr - buff < bytes - 1;
ptr++)
;
int c = *ptr;
*ptr = '\0';
if (c != EOF && c != '\n') {
while ((c = getchar()) != '\n' && c != EOF)
;
}
return buff;
}
int readint() {
char buffer[256] = "";
readstr(buffer, 256);
return atoi(strpbrk(buffer, "0123456789"));
}
double readfloat() {
char buffer[256] = "";
readstr(buffer, 256);
return atof(strpbrk(buffer, "0123456789"));
}
char readchar() {
char buffer[2] = "";
readstr(buffer, 2);
return *buffer;
}
until now, I wrote these ones. Any advice or suggestion? a more elegant one or simpler solution? apparently they work, but I don't know if this is the best approach.
Upvotes: 0
Views: 954
Reputation: 144770
Your goal seems to read a line of input into a buffer with a given size, not storing the trailing newline and discarding any excess characters present on the line. You do need to read the newline so it does not linger in the input stream, but you do not want to store it into the destination array as fgets()
does.
Note that this can be achieved with scanf()
this way:'
char buf[100] = "";
if (scanf("%99[^\n]", buf) == EOF) {
// handle end of file
} else {
// line was read into buf or empty line was detected and buf was not modified
scanf("%*[^\n]"); // consume extra bytes on the line if any
scanf("%*1[\n]"); // consume the newline if present
}
This is very cumbersome, and it gets even worse if the buffer size is a variable value. As many savvy C programmers noted, scanf
is not buggy, it is just difficult to use correctly. I would even say its semantics are confusing and error prone, yet it can be used safely, unlike gets()
, which was removed from recent versions of the C Standard because any arbitrary long input can cause undefined behavior with it.
Reading a line and discarding the trailing newline can be done with fgets()
, but combining the reading the line, discarding extra bytes and consuming the newline may be useful as a separate function.
There are problems in your code:
you store the return value of getchar()
directly into the char
array, removing the distinction between EOF
and the char value '\377'
on architectures with signed chars, while *ptr != EOF
would never match on architectures where char
is unsigned by default. You must store it into an int
variable.
there is no way to tell if the end of file was reached: the function returns an empty string at end of file, just like it does for blank lines int the stream
the buffer size cannot be zero.
truncation cannot be detected: instead of returning the destination array, you could return the number of characters needed for the full line and -1 at end of file.
calling atoi(strpbrk(buffer, "0123456789"))
poses multiple problems: strpbrk
can return a null pointer, causing undefined behavior, it will skip a leading sign, and atoi
is not fully defined for contents representing values out of range for type int
.
Here is a modified version:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int readstr(char *buff, size_t bytes) {
size_t pos = 0;
int c;
while ((c = getchar()) != EOF && c != '\n') {
if (pos + 1 < bytes)
buff[pos] = (char)c;
pos++;
}
if (size > 0) {
if (pos < size)
buff[pos] = '\0';
else
buff[size - 1] = '\0';
}
if (c == EOF && pos == 0)
return -1;
return (int)pos;
}
int readint(void) {
char buffer[256];
long n;
if (readstr(buffer, sizeof buffer) < 0)
return -1;
n = strtol(buffer, NULL, 0);
#if LONG_MIN < INT_MIN
if (n < INT_MIN) {
errno = ERANGE;
return INT_MIN;
}
#endif
#if LONG_MAX > INT_MAX
if (n > INT_MAX) {
errno = ERANGE;
return INT_MAX;
}
#endif
return (int)n;
}
double readdouble(void) {
char buffer[256];
if (readstr(buffer, sizeof buffer) < 0)
return NAN;
else
return strtod(buffer, NULL);
}
int readchar(void) {
char buffer[2] = "";
if (readstr(buffer, sizeof buffer) < 0)
return EOF;
else
return (unsigned char)*buffer;
}
Upvotes: 1