Click to See Complete Forum and Search --> : EscapeCharacter::


TheLinuxDuck
08-07-2001, 04:03 PM
Ok, dood... I've been thinking about your request for an input routine that allocates enough space for a user input'd string, and no more.

I've been trying to come up with a solution since the other day, and finally made a breakthrough today.

The problem that I have been running into is that without editing the terminal settings, any input function (from stdin) will wait for a newline before continuing. I couldn't find any reasonable way to determine if there was data waiting in the input buffer... until today.

The way that fgets works, it will allow you to limit how much data is used from a file stream. We use stdin and are able to limit user input.

If they enter less than our limit amount, the resultant string will contain a newline character. If we were to make a call to fgets again at this point, it would wait again for another newline.

If they enter equal to (I think) or greater than our limit amount, then the resultant string will not contain a newline. And, if we make a call to fgets at this point, it will not pause and wait for the user to press enter, because there is already a newline in the input buffer.

Do you see the dilemma? We can't just call fgets after we've gotten some input, because it may wait for the user to press enter, or it may not.

The solution, I found, is to check the resultant string for the existance of a newline. If we find a newline in the resultant string, then we know that the buffer was cleared, and we don't need to get anything else from the buffer. If we don't find a newline, we know that there is one waiting (since fgets reacts to a newline), so calling fgets will not pause.

Anyhow, here is the code I came up with.. I tested it with several different numbers and buffer sizes and stuff, and it seems to work ok.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//
#define MAXINPUTBUF 128 // read buffer for data from stdin
#define MAXINPUTSIZE 16384 // max size for any string
//
char *userInput(const char *inputPrompt,const int stringLimit);
//
int main(void)
{
char *buffer; // pointer to input'd data
//
buffer=userInput("Enter text: ", 300); // give prompt and max length
if(buffer!=NULL) { // if input routine succeeded
printf("User entered: %s\nlength: %d\n",buffer,strlen(buffer));
free(buffer);
}
return 0;
}
//
char *userInput(const char *inputPrompt,const int stringLimit)
{
char storageBuffer[MAXINPUTBUF]; // a buffer for segmented input gathering
char *newPtr=NULL; // to handle new mem allocation
char *tempPtr; // used for realloc, to preserve newPtr
unsigned int size; // determines how much to allocate at
// any given time
//
// Only display an input prompt if the user didn't pass in a NULL
//
if(inputPrompt!=NULL) {
printf("%s", inputPrompt);
fflush(stdout);
}
//
// If the user tries to do an illegal string size, return a NULL
//
if(stringLimit<1 || stringLimit>MAXINPUTSIZE) return NULL;
//
memset(storageBuffer,0,MAXINPUTBUF); // reset storageBuffer to zero
//
// As long as we don't find a newline in the data received from fgets, we
// know that we didn't reach the end of the input (meaning another call to
// fgets will not wait for the user to press enter again.
//
while(!strrchr(storageBuffer,'\n')) {
//
// Get data from stdin. If there is nothing waiting (meaning no newline in
// the stdin buffer), then we wait for the user to type something and
// press enter.
//
fgets(storageBuffer,MAXINPUTBUF,stdin);
size=strlen(storageBuffer); // determine how much was read
//
// newPtr will only equal NULL if this is the first time through the
// 'while' loop. That means we're starting from scratch.
//
if(newPtr==NULL) {
//
// To determine how much space to allocate, we need to make sure that
// we're not going to create a string longer than the length specified
// by stringLimit.
//
size=size<stringLimit?size:stringLimit;

newPtr=(char *)malloc(size); // if malloc fails, we don't have much
if(newPtr==NULL) return NULL; // choice but to quit and return NULL
newPtr[0]='\0'; // tell strcat the string is empty
}
else {
//
// If we get here, we're at least on the second loop through 'while'.
// Now, we have to determine how much extra space to allocate, since
// there is already data in newPtr. We determine this by making sure
// that what is already there plus what we just got aren't greater than
// the stringLimit size
//
size=(size+strlen(newPtr))<stringLimit?size+strlen (newPtr):stringLimit;
tempPtr=(char *)realloc(newPtr,size); // if this call fails, we return
if(tempPtr==NULL) return newPtr; // what bit we've allocated so far
newPtr=tempPtr;
}
//
// Only copy as much as leaves our string under the specified size
// (stringLimit)
//
strncat(newPtr,storageBuffer,size-strlen(newPtr));
//
// Make sure to remove any unwanted newline from the string
//
if(newPtr[strlen(newPtr)-1]=='\n')
newPtr[strlen(newPtr)-1]='\0';
//
// If our string has reached it's peak size, we make sure that the 'while'
// loop will stop, since it looks for the existance of a newline in
// storageBuffer
//
if(strlen(newPtr)==stringLimit) storageBuffer[0]='\n';
}
//
// Make sure the input buffer has nothing left
//
fseek(stdin,0L,SEEK_END);
//
// return our new allocated string
//
return newPtr;
}


All:
Please let me know if there are any errors (and a solution, too, if you feel like messing with it). Also, I would love to see someone optimize that code. I feel like it's a little lengthy. Anyone have any pointers or ideas on how to make it a little more compact (minus removing the comments.. ::smile: :)?

EscapeCharacter
08-07-2001, 05:38 PM
damn you're crazy LinuxDuck :) thats cool im gonna have to play with that. throw that thing up on CCAE. thanks for taking the time on that its gonna give me alot of new functions ive never used to play with.

TheLinuxDuck
08-07-2001, 06:04 PM
Originally posted by EscapeCharacter:
<STRONG>damn you're crazy LinuxDuck :)</STRONG>

Heheh.. maybe a little bit.. (^= But, I learned in the process... I have a tendency to persist with something when someone else is looking for an answer... (it's when I want the answer that I don't always spend the time)

<STRONG>thats cool im gonna have to play with that. thanks for taking the time on that its gonna give me alot of new functions ive never used to play with.</STRONG>

Well, if it helps you learn, then it is worth every second it took. Besides, there is more than likely a better way to do it, but I don't know it. (^=

<STRONG>
throw that thing up on CCAE.
</STRONG>

I will after it has been tested a little more. I don't feel like the testing I did was enough, so I would like to give it some time to try and find bugs, and hopefully, someone will have a way to optimize it a bit more..

Then I think it will go to CCAE. (^=

Btw, just so you know, I had fun doing this. (^=

EscapeCharacter
08-07-2001, 06:17 PM
Originally posted by TheLinuxDuck:
<STRONG> Btw, just so you know, I had fun doing this. (^=</STRONG>

who doesnt :)
i have another weird problem now it seem that when i compile a program with gcc with just a normal loop in it i.e.
for(x; x&lt; 10; x+=4)
it never executes, though when i compile the exact same file with g++ the loop executes, weird huh?

[ 07 August 2001: Message edited by: EscapeCharacter ]

jemfinch
08-07-2001, 08:09 PM
You don't initialize x in that loop. Do you intialize it earlier in the code?

Jeremy

EscapeCharacter
08-08-2001, 02:31 AM
yeah i figure out what i was doing wrong i accidently commented out a line i wasnt suppose to and left the one i was suppose there

sans-hubris
08-08-2001, 03:12 AM
First, I'd leave the prompt up to the user of the function (i.e. the first argument isn't necessary, IMO, the user of the function should take care of that if he/she wants to.) Second, isn't there a way to get charactors out of stdin one at a time until you reach a new line charactor? I was just checking Dinkum (great reference, BTW, IMO) and you might be better off using fgetc (http://www.dinkum.com/htm_cl/stdio.html#fgetc) in a loop instead. You can then just probably just realloc() memory for every five bytes or something.

[ 08 August 2001: Message edited by: Muad Dib --formerly ndogg ]

TheLinuxDuck
08-08-2001, 09:12 AM
Originally posted by Muad Dib --formerly ndogg:
<STRONG>First, I'd leave the prompt up to the user of the function (i.e. the first argument isn't necessary, IMO, the user of the function should take care of that if he/she wants to.)</STRONG>

I don't agree that it shouldn't be a part of the function. A necessary part of having input is providing some kind of prompt to indicate what the input is expecting. Why make the coder have to handle it, when it's easy to give them the option?

<STRONG>Second, isn't there a way to get charactors out of stdin one at a time until you reach a new line charactor? I was just checking Dinkum (great reference, BTW, IMO) and you might be better off using fgetc in a loop instead. You can then just probably just realloc() memory for every five bytes or something.</STRONG>

Consider how many times realloc will be called on a standard input, like a users name. If the user's full name is 35-45 characters, we're talking about 7-9 realloc calls. With a buffer like this, of 128 chars (or MAXINPUTBUF), there is only 1 call to realloc, and the allocated memory is exactly the size of the input, instead of rounded off to each 5 bytes.

Or, consider a brief comment input, say no more than 80-90 chars. Reallocing every 5 chars would result in 16-18 calls, not to mention having to call fgetc for every single character. With this buffer method, it is only 1 call for each.

Personally, I think going char by char is slow. I think the CPU is spending more time calling functions than actually processing the data it has read.

I apologize, but I disagree with that as well. (^=

nanode
08-08-2001, 11:16 AM
haha -

I had to solve a very similar problem yesterday, but using java swing JTextField instead of the console.

Unfortunately I had to work 1 char at a time. :(

TheLinuxDuck
08-08-2001, 11:52 AM
Muad Dib:

Ok, so I feel bad for not being more open to your suggestions, so I have re-written the function to act as you have suggested. I removed most of the comments, since the general method is the same.


#define ALLOCCHUNKSIZE 5
#define MAXINPUTSIZE 16384
//
char *userInput(const int stringLimit)
{
char storageBuffer; // a buffer for single char input gathering
char *newPtr=NULL; // to handle new mem allocation
char *tempPtr; // used for realloc, to preserve newPtr
unsigned int size; // determines how much to allocate at
// any given time
if(stringLimit&lt;1 || stringLimit&gt;MAXINPUTSIZE) return NULL;
//
storageBuffer=0; // reset storageBuffer to zero
while(storageBuffer!='\n') {
storageBuffer=fgetc(stdin);
if(newPtr==NULL) {
size=ALLOCCHUNKSIZE&lt;stringLimit?ALLOCCHUNKSIZE:str ingLimit;
newPtr=(char *)malloc(size+1); // if malloc fails, we don't have
if(newPtr==NULL) return NULL; // much choice, quit and return NULL
newPtr[0]='\0'; // tell strlen the string is empty
}
else {
if(strlen(newPtr)&gt;1 && strlen(newPtr)%5==0) {
size=(ALLOCCHUNKSIZE+strlen(newPtr))&lt;stringLimit?
ALLOCCHUNKSIZE+strlen(newPtr):
stringLimit;
tempPtr=(char *)realloc(newPtr,size+1);// if this call fails, we return
if(tempPtr==NULL) return newPtr; // what we've allocated so far
newPtr=tempPtr;
}
}
if(strlen(newPtr)&lt;stringLimit && storageBuffer!='\n') {
newPtr[strlen(newPtr)+1]='\0';
newPtr[strlen(newPtr)]=storageBuffer;
}
}
fseek(stdin,0L,SEEK_END);
return newPtr;
}

EscapeCharacter
08-08-2001, 04:58 PM
Originally posted by TheLinuxDuck:
<STRONG>Muad Dib:

Ok, so I feel bad for not being more open to your suggestions, so I have re-written the function to act as you have suggested. I removed most of the comments, since the general method is the same.


#define ALLOCCHUNKSIZE 5
#define MAXINPUTSIZE 16384
//
char *userInput(const int stringLimit)
{
char storageBuffer; // a buffer for single char input gathering
char *newPtr=NULL; // to handle new mem allocation
char *tempPtr; // used for realloc, to preserve newPtr
unsigned int size; // determines how much to allocate at
// any given time
if(stringLimit&lt;1 || stringLimit&gt;MAXINPUTSIZE) return NULL;
//
storageBuffer=0; // reset storageBuffer to zero
while(storageBuffer!='\n') {
storageBuffer=fgetc(stdin);
if(newPtr==NULL) {
size=ALLOCCHUNKSIZE&lt;stringLimit?ALLOCCHUNKSIZE:str ingLimit;
newPtr=(char *)malloc(size+1); // if malloc fails, we don't have
if(newPtr==NULL) return NULL; // much choice, quit and return NULL
newPtr[0]='\0'; // tell strlen the string is empty
}
else {
if(strlen(newPtr)&gt;1 && strlen(newPtr)%5==0) {
size=(ALLOCCHUNKSIZE+strlen(newPtr))&lt;stringLimit?
ALLOCCHUNKSIZE+strlen(newPtr):
stringLimit;
tempPtr=(char *)realloc(newPtr,size+1);// if this call fails, we return
if(tempPtr==NULL) return newPtr; // what we've allocated so far
newPtr=tempPtr;
}
}
if(strlen(newPtr)&lt;stringLimit && storageBuffer!='\n') {
newPtr[strlen(newPtr)+1]='\0';
newPtr[strlen(newPtr)]=storageBuffer;
}
}
fseek(stdin,0L,SEEK_END);
return newPtr;
}
</STRONG>

something about this is bugging me so hopefully you guys can shed some light on it for me, when you start the while loop you have the call to fgetc this loops for the length of the the returned value of that but what i dont get is if i put something like

while(storageBuffer!='\n'){
storageBuffer=fgetc(stdin);
printf("Garbage");

the printf("Garbage"); executes many times but im only asked for input once. i would think that fgetc would be expecting more input with each loop but its not, its being skipped so whats the deal with that?

TheLinuxDuck
08-08-2001, 05:12 PM
(^=

Buffered input, dood.. buffered input.

When you make a call to read input from stdin, the function looks for data in the input stream.

If there is no data, it will wait until you have pressed enter, no matter how many keys you do or don't press before then.

If there *is* data in the buffer, the call will read however many characters it was told to read, or stop when a newline is reached, without waiting for input from the keyboard.

Take this example:

The buffer is empty, and the code issues the command:

fgets(buffer,10,stdin);


So, you type in your name, and it is actually 15 characters. fgets doesn't do anything with your input until you press enter, as buffered input will do.

So now, the contents of buffer is "EscapeCha".

There is still data in the buffer. If you make another call to fgets at this point, it will react to what is left in the buffer, unless you specifically clear the buffer. Remember that fgets is looking for a newline.
So a call:

fgets(buffer2,10,stdin);

:will cause buffer2 to contain "racter\n" (including the newline).

That's why the subsequent calls to fgetc don't pause.

Make sense?

EscapeCharacter
08-08-2001, 05:57 PM
ah, i see said the blind man :), makes sense. its all in the buffer, its all in the buffer, its all in the buffer...

[ 08 August 2001: Message edited by: EscapeCharacter ]