User input in C

Discussion in 'Mac Programming' started by iEdd, Mar 11, 2011.

  1. iEdd macrumors 68000

    iEdd

    Joined:
    Aug 8, 2005
    Location:
    Australia
    #1
    I'm after a way to read user input in C, ideally as whitespace-separated strings.

    Basically, I'm after something that works exactly the same way *argv[] works when launching the program, but using it for prompting/interaction.

    eg.
    Code:
    $ [color=blue]./program new 2 green[/color]
    
    and I can use argv[1], argv[2], argv[3] to read these strings and argc to check how many arguments were given.

    but what about when my program is running and I need user input:
    Code:
    Enter command: [color=blue]save /file.txt[/color]
    
    or
    Code:
    Enter command: [color=blue]set strength 10[/color]
    
    Is there a simple way to read the blue bits from the user in a way that works like *argv[] on program execution?

    Thanks
     
  2. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #2
    If you don't want to do a full-on parser that will handle things like quoted strings, perhaps use the strtok function.

    Start with a call to fgets to read in the whole line. Then call strtok with the whole line as the first argument and a string of separators as the second argument. The returned result will be a string containing the first "word" in the input.

    Call strtok again repeatedly but with NULL as the first parameter to get the rest of the "words". It will return NULL when there's no more words.

    References:
    strtok http://developer.apple.com/library/mac/#documentation/darwin/reference/manpages/man3/strtok.3.html
    fgets http://developer.apple.com/library/mac/#documentation/darwin/reference/manpages/man3/fgets.3.html
     
  3. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #4
    Remember that on OS X, you're using a Unix system. This documentation is available straight from the command line, no need for web access. Just type man 3 strtok or man 3 fgets.

    One point no one seems to address is that fgets will only accept up to int n characters. If your user inputs more, you won't have the entire string read in. You need to check that the last character inputted is \n. If it's not, you need to grow your buffer and read in the rest of the string.

    While you may think that static value of 1024 you supply is long enough for anyone, someone trying to deliberately mangle your program's input in search of buffer overflows could cause harm. Better just be ready. You should read up on malloc/realloc to dynamically manage your input buffer and make sure to call fgets multiple times if needed.
     
  4. iEdd thread starter macrumors 68000

    iEdd

    Joined:
    Aug 8, 2005
    Location:
    Australia
    #5
    Thanks for the replies.

    I found exactly what I was after:
    http://www.lemoda.net/c/split-on-whitespace/index.html

    However, I can't use that, because it's not my work and it's way more than a snippet.

    I settled on fgets(), and instead of tokenising (which has far too many pitfalls), I'm just incrementing a pointer on whitespace to find each word in the string.

    At the moment, it's a fixed buffer, but I may need to make a *while char is not '\n', + 1. malloc that*.
     
  5. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #6
    But that is what strtok() and strsep() does, are you comparing the words letter by letter to find a match, if you don't separate the string?

    The last paragraph can be done by strlen().
     
  6. KnightWRX, Mar 13, 2011
    Last edited: Mar 13, 2011

    KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #7
    You mean getline() right ? That is a POSIX function, not an ANSI one though. And it basically uses... realloc() on your buffer in order to read the entire line.

    His last paragraph is a reference to my reference of fgets()'s problematic use of a fixed-sized buffer.

    EDIT : Working on server maintenance so trying to pass the time, so here's my over-engineered unoptimized solution :

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define BUFFER_SIZE 5 
    
    int main(int argc, char ** argv)
    {
    	char * buffer = NULL;
    	int buffersize = BUFFER_SIZE * sizeof(char);
    	char * tempbuffer = malloc(BUFFER_SIZE * sizeof(char));
    	do
    	{
    		if(!buffer)
    		{
    			buffer = malloc(buffersize);
    			printf("Input string : ");
    	                fflush(stdout);
    		}
    		else
    		{
    			buffersize += (BUFFER_SIZE * sizeof(char));
    			printf("Buffer was not long enough, reallocating to %d bytes\n", buffersize);
    			buffer = realloc(buffer, buffersize); 
    		}
    
    		fgets(tempbuffer, BUFFER_SIZE * sizeof(char), stdin);
    		buffer = strcat(buffer, tempbuffer);
    		printf("We got : \"%s\"\n", buffer);
    	} while(buffer[strlen(buffer) - 1] != '\n');
    	
    	return EXIT_SUCCESS;
    }
    
    Feel free the simplify. Yes, 5 is a ridiculously small buffer. It is obviously used to provide an actual example of it working :

    Code:
    $ ./test
    Input string : Hello Macrumors, this string is too long !
    We got : "Hell"
    Buffer was not long enough, reallocating to 10 bytes
    We got : "Hello Ma"
    Buffer was not long enough, reallocating to 15 bytes
    We got : "Hello Macrum"
    Buffer was not long enough, reallocating to 20 bytes
    We got : "Hello Macrumors,"
    Buffer was not long enough, reallocating to 25 bytes
    We got : "Hello Macrumors, thi"
    Buffer was not long enough, reallocating to 30 bytes
    We got : "Hello Macrumors, this st"
    Buffer was not long enough, reallocating to 35 bytes
    We got : "Hello Macrumors, this string"
    Buffer was not long enough, reallocating to 40 bytes
    We got : "Hello Macrumors, this string is "
    Buffer was not long enough, reallocating to 45 bytes
    We got : "Hello Macrumors, this string is too "
    Buffer was not long enough, reallocating to 50 bytes
    We got : "Hello Macrumors, this string is too long"
    Buffer was not long enough, reallocating to 55 bytes
    We got : "Hello Macrumors, this string is too long !
    "
    
     
  7. iEdd, Mar 13, 2011
    Last edited: Mar 13, 2011

    iEdd thread starter macrumors 68000

    iEdd

    Joined:
    Aug 8, 2005
    Location:
    Australia
    #8
    Thanks Knight, that's basically the same as what I ended up working out yesterday.

    Mine makes an initial buffer of 5 chars, then if a '\n' isn't found, it gives the buffer another 5 chars and writes SIX characters into the new buffer (to overwrite the '\0' at the end of the old block).

    Edit: Basically that means your statement to check for null isn't needed in mine, and I guess I did strcat manually by overlapping on the terminator.
     
  8. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #9
    No, instead of iterating over the line, counting characters until '\n', then use that to malloc, as explained. You can of course use strlen() to get the length of the string.
     
  9. KnightWRX, Mar 13, 2011
    Last edited: Mar 13, 2011

    KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #10
    Just be careful of buffer overflows. Writing this kind of code is exactly the cause of many exploits out there in the wild. ;)

    You could go larger than 5 chars at a time to make less iterations at the cost of additional memory use on the last iteration. You could also read in 1 char by 1 to make sure to never use up too much memory. A compromise on what you want your function to cost. Of course, this being 2011 and both CPU and RAM being available in mass quantities makes these kind of optimizations quite useless.


    Sure you can, but you need to read it in from the OS's stdin buffer first. It would be easy to be able to strlen() the input buffer, but alas, no one said programming was supposed to be easy. I think you're missing this part of the equation, check my code and specifically the parameters passed to fgets() and you'll see what we were actually discussing. His last paragraph was again a reference to my reference of fgets()'s fixed sized buffer issues when reading in input from the user (specifically stdin), which might leave some characters in the buffer.

    getline() of course solves that on Posix systems. Other systems require the type of code me and iEdd wrote using fgets() and realloc().

    Of course, if you're doing fgets() input on a string itself (which is technically possible if not completely useless... strncpy() anyone ?), then you can just pass the 2nd parameter as a strlen() of the 3rd parameter.
     
  10. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #11
    What are you talking about? This is the sentence I referred to:

    "At the moment, it's a fixed buffer, but I may need to make a *while char is not '\n', + 1. malloc that*"

    Being able to scan through it looking for '\n' is assuming it's in a buffer. Even if you use fgets with a fair buffer size say 1024 then you can re-write that buffer on each pass. Having an upper bound is normal, infinite length input is not possible anyway, so why not just use BUFSIZ as an upper bound, then beyond that assume the program is being used in a non legitimate way and issue an error.
     
  11. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #12
    And that sentence is exactly what I was referring to. This was a sub-thread and not part of the original post. That sentence was a reference to my reference of fgets()'s fixed sized buffer issues when reading from stdin. strlen() doesn't solve that since you can't strlen(stdin); unfortunately.

    PM me if you need this explained further or read my post and code, I think I have been clear enough and iEdd has too.

    Again, read my post, we're talking about stdin as the buffer specifically.
     
  12. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #13
    Im not referring to stdin, I'm referring to a buffer used by fgets() it needs one. I don't need to get this explained, this is a bit ridiculous don't you think? I mean it's such a trivial problem after all.
     
  13. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #14
    PM sent. No need to carry this on in this thread at this point.
     
  14. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #15
    I'm giving an advise, you are disputing the validity of it. I don't see why it can't be discussed here as it's clearly on topic. I'm not talking about stdin, as I said you can use a buffer that you over write on each pass.
     
  15. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #16
    But like I told you many times we were :

    Hence why your strlen() suggestion is missing the point and your continued arguing in this thread should be taken to PM if you have an issue. Your advice about a stdin reference while not discussing stdin was not valid in the context it was given, like I pointed out.

    Lets keep this in PM for now, this is derailing the thread.
     
  16. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #17
    Look, if your simply counting characters one by one from stdin, even though that is possible. Once you have the length it's useless, since your input string is gone. You need at least a temporary buffer.

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main()
    {
        char buf[3] = {0};
        size_t length = 0;
    
        while( fgets(buf, 3, stdin) != 0 ) {
            length = strlen(buf);
            // allocate here
            printf("%lu\n", length);
        }
    
    
        return 0;
    }
    
    Here fgets() will be called until the entire input is consumed.
     
  17. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #18
    Please correct me if I'm wrong, but if the user enters multi-line input like:
    Code:
    Zaphod
    Beeblebrox
    is a really cool cat
    who always knows
    where his towel is
    Your example code will keep at it until all lines are consumed. (It basically acts like "cat.")

    It seems like the desired behavior here is to terminate at the first EOL, and simply looking at strlen doesn't seem to let you find that. For lines that don't have an exact multiple of 3 characters you would be able to detect the EOL by when length != 3, but otherwise...

    B
     
  18. subsonix macrumors 68040

    Joined:
    Feb 2, 2008
    #19
    I guess so, but assuming it's used as a live interpreter it would not be possible to enter more than one line at a time. But yeah, if not, you would have to use something like strchr() or similar I guess.
     
  19. iEdd thread starter macrumors 68000

    iEdd

    Joined:
    Aug 8, 2005
    Location:
    Australia
    #20
    I lol'd though. Thankyou both for the contributions, even if there was some confusion. I also apologise for my atrocious pseudo code (the sentence in question).

    To clear things up though, I'm referring to two buffers - stdin and the one that I continually realloc (buffer in KnightWRX's code).

    Couple of questions though, KnightWRX:
    - What does flushing the output stream [fflush(stdout)] do in this code?
    (I don't have that entire if statement in my code because I start with an initial buffer anyway.)

    - Do you have an example of this? I'm aware of gets() being deprecated for this reason, but I'm curious how it happens with fgets() (Not that it really matters in my case as I'm just making a terminal game, where I've tested my code with the largest string Terminal.app will allow to make sure the buffer is expanding properly.)

    Thanks again.
     
  20. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #21
    That was just me being fancy. From the spec, printf() doesn't guarantee a flush of the stdout/stderr/any other buffer to the user output if you don't have a carriage return in your string. Since I wanted my input string to show up on the same line as my "Input String:" prompt I thus omitted the '\n' from the printf statement.

    This usually works fine on Borland's runtime, but on GNU libc, I've found they use the other approach of waiting to flush the buffer and thus the prompt would not show up. fflush(stdout) forces the output to happen regardless.

    Not really an example, just saying to be careful. I had an initial bug in my code while writing it where my realloc statement had a different size fed to it than what I wanted which resulted in a massive overflow after the strcat() call. It worked fine for a few iterations then started showing very weird output on the printf() call with buffer. Make sure to debug some and be sure realloc gets fed the proper sizes so that you don't end up thinking your buffer is bigger than it is. ;)
     
  21. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #22
    This is a diversion of the thread but I've just got to comment and recount about this references to Borland's runtime.

    I'm surprised anyone knows or remembers how the Borland runtime behaved. My first compiler was Borland C++ 4.5 on Win 3.1. It was so big (or my hard disk was so small) that I had to choose between installing it or installing anything else. I'm a nerd, so it wasn't too hard of a choice. :D But I haven't thought about Borland since going to Java around 1997! When I came back to C++ years later, it was gcc on linux. No IDE, I'd matured into make and vim. :D

    Thanks for jogging the memory.
     
  22. iEdd thread starter macrumors 68000

    iEdd

    Joined:
    Aug 8, 2005
    Location:
    Australia
    #23
    Cheers KnightWRX. I appreciate all the time you (and others) have spent in this thread. :)

    As for going off-topic, it's all good - it's all related to learning more about C, which is sort of what this thread is about. I'll lookup Borland.
     
  23. jiminaus macrumors 65816

    jiminaus

    Joined:
    Dec 16, 2010
    Location:
    Sydney
    #24
    I just looked it up myself to see where it had gone. There was no mention of any programming tools at borland.com! Then I read the Borland Wikipedia page. In 2008, Borland's programming tools were sold to Embarcadero. C++ Builder (the evolution of Borland C++) now lives here: http://www.embarcadero.com/products/cbuilder
     
  24. KnightWRX macrumors Pentium

    KnightWRX

    Joined:
    Jan 28, 2009
    Location:
    Quebec, Canada
    #25
    I spent most of my time in the late 90s on the blue and grey screens of Turbo C++ for DOS (version 3.0 ?). Cheap, small and "good enough" to learn straight ANSI C on. Now that you mention it, I've just had a bit of nostalgia, I had to look it up :

    [​IMG]

    (OMG conio.h and clrscr()! What an abomination!) It seems Borland eventually gave it away free : http://www.top4download.com/turbo-c-/aklqwuba.html. Now I'm just going to have to see if that runs in DOSBox. :D

    I migrated to VI and Makefiles on Linux too after a quick dabble in the darkside of Win32 (read Charles Petzold's work and worked on OpenGL stuff using Microsoft's then slow as molasse opengl32.dll).

    All this time writing out code and I never made anything worthwhile in C. I think the only actual piece of code I finished beyond assignments was a daemon that could run a few network diagnostics, which was part of a web app I was writing to run traceroutes/pings to gather statistics at an earlier ISP gig I had.

    Good luck iEdd! My terminal based ANSI-C game I'm still working on moved on to OpenGL on Windows, to SDL/Mesa on Linux to GTKGLArea on X11 and now to iOS using Quartz 2D (sad to see the GL code go, but the Quartz versions now does about 3 times more than any GL code I managed to make work).
     

Share This Page