On 2010-08-24, parjit <> wrote:
> I'm working on a small c project. I need to read lines from stdin, but
> because gets is known to be an undefined function I've been given the
> code below to use instead. It works fine but I'd like to understand what
> its doing and I just find it really confusing.
That is because this code is confusing.
> Can anyone explain to me what's going on here. Thanks.
> #include <string.h>
> char *safegets(char *buf, int sz)
> {
> char *r,*p;
> return (buf==NULL || sz<1) ? NULL :
> sz==1 ? *buf='\0', buf :
> !(r=fgets(buf,sz,stdin)) ? r :
> (p = strchr(buf, '\n')) ? *p=0, r : r;
> }
Okay, first a top-level view: Someone who thinks he's a lot smarter than
he actually is is trying to show off. I used to write code like this,
maybe fifteen or twenty years ago.
Okay, let's start by expanding this a bit:
char *safegets(char *buf, int size)
{
char *temp1, *temp2;
return (buf == NULL || size < 1) ? NULL :
size == 1 ? (*buf = '\0', buf) :
!(temp1 = fgets(buf, size, stdin)) ? temp1 :
(temp2 = strchr(buf, '\n')) ? (*temp2 = 0, temp1) :
temp1;
}
Wow, that's awful.
Quick intro:
return x ? y : z
is basically the same as
if (x) {
return y;
} else {
return z;
}
?: is like an if/then, except you use it as part of an expression,
instead of on statements. So you could do something like
abs_of_x = (x < 0) ? (-1 * x) : (x);
and that's the same effect as
if (x < 0) {
abs_of_x = (-1 * x);
} else {
abs_of_x = x;
}
Let's convert that to if/else:
char *safegets(char *buf, int size);
{
char *temp1, *temp2;
if (buf == NULL || size < 1) {
return NULL;
} else if (size == 1) {
*buf = '\0';
return buf;
} else if (!(temp1 = fgets(buf, size, stdin))) {
return temp1;
} else if (temp2 = strchr(buf, '\n')) {
*temp2 = 0;
return temp1;
} else {
return temp1;
}
}
Okay, this is still crap. I would not approve this code in a tech
review but it's getting legible. Lemme give it some comments:
/* read at most size characters into buf, resulting in a
* null-terminated string. If buf is null or size is not
* at least 1, returns null. If fgets() fails, returns
* null. Otherwise, returns buf, with the first newline
* (if any) replaced with a null byte. Since fgets() in
* theory stops with the newline, that just trims a trailing
* newline.
*/
char *safegets(char *buf, int size);
{
char *temp1, *temp2;
if (buf == NULL || size < 1) {
/* if buf is NULL, or we've said that less than
* one byte is available, return a NULL pointer;
* you give me invalid inputs, I give you invalid
* outputs.
*/
return NULL;
} else if (size == 1) {
/* if we have exactly one byte, it has to be
* the null terminator.
*/
*buf = '\0';
return buf;
} else if (!(temp1 = fgets(buf, size, stdin))) {
/* assign the results of fgets() into a
* new value named temp1. If it's "false"
* (a null pointer), return that newly
* generated null pointer.
*/
return temp1;
} else if (temp2 = strchr(buf, '\n')) {
/* if we find a newline in the string, replace
* it with a null byte. Return the pointer
* returned by fgets. Which is identical to buf,
* mind you.
*/
*temp2 = 0;
return temp1;
} else {
/* there was no newline, but we got a string,
* return it unmodified.
*/
return temp1;
}
}
Ugh. Not very consistent, but at least it makes sense.
char *safegets(char *buf, int size);
{
char *temp1, *temp2;
if (buf == NULL || size < 1) {
return NULL;
} else if (size == 1) {
*buf = '\0';
return buf;
}
temp1 = fgets(buf, size, stdin);
if (!temp1) {
/* fgets failed */
return NULL;
}
temp2 = strchr(buf, '\n');
if (temp2) {
/* found a newline, trim it */
*temp2 = '\0';
}
return buf;
}
That's a little clearer. Still, it's pretty kludgy. Let's see
if we can't make it a little friendlier. At this point, it's more
clear what the goal is: The goal is to write a wrapper around fgets()
which strips the trailing newline. For historical reasons, "gets()"
trims the terminating newline, fgets() doesn't.
Here's a first pass:
char *safegets(char *buf, int size) {
int c;
int count = 1;
char *s = buf;
if (s && size > 0) {
while ((c = getchar()) != EOF &&
c != '\n' &&
count++ < size) {
*s++ = c;
}
*s++ = '\0';
return buf;
} else {
/* invalid arguments */
return NULL;
}
}
Here's my test program:
#include <stdio.h>
char *safegets(char *buf, int size) {
int c;
int count = 1;
char *s = buf;
if (s && size > 0) {
while ((c = getchar()) != EOF &&
c != '\n' &&
count++ < size) {
*s++ = c;
}
*s++ = '\0';
return buf;
} else {
/* invalid arguments */
return NULL;
}
}
int
main(void) {
char buf[10] = { "xxxxxxxxx" };
int i;
printf("doing safegets, length 6:\n");
safegets(buf, 6);
for (i = 0; i < 8; ++i) {
printf("\t[%d]: 0x%02x [%c]\n",
i,
(unsigned char) buf[i],
isprint((unsigned char) buf[i]) ?
buf[i] : '.');
}
printf("[5] or earlier should be 0x00 [.], following should be [x].\n");
return 0;
}
This passed the obvious edge cases:
* values less than 5 characters long
* exactly 5 characters plus a newline
* newline with following characters
* more than 6 characters
-s
--
Copyright 2010, all wrongs reversed. Peter Seebach /
usenet-
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!