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: :)?
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: :)?