51

I want to read the name entered by my user using C programmes.

For this I wrote:

char name[20];

printf("Enter name: ");
gets(name);

But using gets is not good, so what is a better way?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peeyush
  • 4,728
  • 16
  • 64
  • 92

8 Answers8

93

You should never use gets (or scanf with an unbounded string size) since that opens you up to buffer overflows. Use the fgets with a stdin handle since it allows you to limit the data that will be placed in your buffer.

Here's a little snippet I use for line input from the user:

#include <stdio.h>
#include <string.h>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

This allows me to set the maximum size, will detect if too much data is entered on the line, and will flush the rest of the line as well so it doesn't affect the next input operation.

You can test it with something like:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("\nNo input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]\n", buff);
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    don't the system libraries that implement scanf prevent overflow of the command ( I understand that within the program if the developer didn't check the input there could be an overflow, but the system library is safe right?). – Marm0t Oct 26 '10 at 13:05
  • 8
    No, if you `scanf("%s")` into a 20 byte buffer and the user enters a 40-byte line, you're hosed. The whole point of `scanf` is scan-formatted and there is little more _unformatted_ than user input :-) – paxdiablo Oct 26 '10 at 13:07
  • 7
    @Marm0t - Think of it this way by considering the following question: how can the implementation prevent an overflow if all it gets is a pointer to a slice of memory (typecasted as a char *) `without any parameter that tells the implementation about the size of the destination buffer`? – luis.espinal Oct 26 '10 at 13:09
  • +1 to paxdiablo for elucidating the problem with scanf and gets – luis.espinal Oct 26 '10 at 13:10
  • @paxdiablo - Good call on the fgets, I just jumped to my bad old C days with my scanf suggestion. I preach using strn functions due to the buffer overflow issue, should have caught myself with the scanf overflow issues. +1 for your solution, and +1 to your comment with the 20 byte/40 byte buffer example! – pstrjds Oct 26 '10 at 13:12
  • 2
    @pstrjds, `scanf` from the console isn't _always_ bad, it can be useful for limited things like numeric input (and homework assignments) and such. But even then, it's not as robust as it should be for a production-quality application. Even when I'm needing to parse the input with a `scanf`-like operation, I'll `fgets` it into a buffer then `sscanf` it from there. – paxdiablo Oct 26 '10 at 13:16
21

I think the best and safest way to read strings entered by the user is using getline()

Here's an example how to do this:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    char *buffer = NULL;
    int read;
    unsigned int len;
    read = getline(&buffer, &len, stdin);
    if (-1 != read)
        puts(buffer);
    else
        printf("No line read...\n");

    printf("Size read: %d\n Len: %d\n", read, len);
    free(buffer);
    return 0;
}
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
joaopauloribeiro
  • 397
  • 2
  • 10
  • 2
    read = getline(&buffer, &len, stdin); gives GCC warnings eg:gcc -Wall -c "getlineEx2.c" getlineEx2.c: In function main : getlineEx2.c:32:5: warning: passing argument 2 of getline from incompatible pointer type [enabled by default] read = getline(&buffer, &len, stdin); ^ In file included from /usr/include/stdio.h:29:0, from getlineEx2.c:24: /usr/include/sys/stdio.h:37:9: note: expected size_t * but argument is of type unsigned int * ssize_t _EXFUN(getline, (char **, size_t *, FILE *)); ^ Compilation finished successfully. – rpd Mar 03 '14 at 10:28
  • 2
    Just updating that getline now requires len to be size_t or unsigned long – Wes Jul 09 '14 at 06:01
  • 2
    Downside: POSIX but not ANSI C. – Ciro Santilli OurBigBook.com Feb 27 '16 at 14:25
6

On a POSIX system, you probably should use getline if it's available.

You also can use Chuck Falconer's public domain ggets function which provides syntax closer to gets but without the problems. (Chuck Falconer's website is no longer available, although archive.org has a copy, and I've made my own page for ggets.)

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • With the caveat that it does not work as expected with files that end only in a CR. Those are not as uncommon as you might imagine (he says, after encountering a folder full of them). It is a missed opportunity that they didn't allow getdelim/getline to take a list of delimiters instead of a single int. – Maury Markowitz Feb 27 '18 at 19:53
  • @MauryMarkowitz It would work on a system that uses CR as its native line-ending format. Text-mode streams will convert whatever the native line-ending type to `\n`. – jamesdlin Feb 28 '18 at 03:57
3

I found an easy and nice solution:

char*string_acquire(char*s,int size,FILE*stream){
    int i;
    fgets(s,size,stream);
    i=strlen(s)-1;
    if(s[i]!='\n') while(getchar()!='\n');
    if(s[i]=='\n') s[i]='\0';
    return s;
}

it's based on fgets but free from '\n' and stdin extra characters (replacing fflush(stdin) doesn't works on all OS, useful if you have to acquire strings after this).

nicanz
  • 347
  • 1
  • 2
  • 11
  • 1
    This should use `fgetc` instead of `getchar` so that it uses the supplied `stream` instead of `stdin`. – jamesdlin Feb 07 '19 at 23:42
1

On BSD systems and Android you can also use fgetln:

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

Like so:

size_t line_len;
const char *line = fgetln(stdin, &line_len);

The line is not null terminated and contains \n (or whatever your platform is using) in the end. It becomes invalid after the next I/O operation on stream. You are allowed to modify the returned line buffer.

wonder.mice
  • 7,227
  • 3
  • 36
  • 39
1

Using scanf removing any blank spaces before the string is typed and limiting the amount of characters to be read:

#define SIZE 100

....

char str[SIZE];

scanf(" %99[^\n]", str);

/* Or even you can do it like this */

scanf(" %99[a-zA-Z0-9 ]", str);

If you do not limit the amount of characters to be read with scanf it can be as dangerous as gets

0

ANSI C unknown maxinum length solution

Just copy from Johannes Schaub's https://stackoverflow.com/a/314422/895245

Don't forget to free the returned pointer once you're done with it.

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;
    
    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

This code uses malloc to allocate 100 chars. Then it fetches char by char from the user. If the user reaches 101 chars, it doubles the buffer with realloc to 200. When 201 is reached, it doubles again to 400 and so on until memory blows.

The reason we double rather say, just adding 100 extra every time, is that increasing the size of a buffer with realloc can lead to a copy of the old buffer, which is a potentially expensive operation.

Arrays must be contiguous in memory because we wan to be able to random access them efficiently by memory address. Therefore if we had in RAM:

content     buffer[0] | buffer[1] | ... | buffer[99] | empty | empty | int i
RAM address 1000      | 1001      |     | 1100       | 1101  | 1102  | 1103

we wouldn't be able to just increase the size of buffer, as it would overwrite our int i. So realloc would need to find another location in memory that has 200 free bytes, and then copy the old 100 bytes there and free the 100 old bytes.

By doubling rather than adding, we quickly reach the order of magnitude of the current string size, since exponentials grow really fast, so only a reasonable number of copies is done.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
-1

You can use scanf function to read string

scanf("%[^\n]",name);

i don't know about other better options to receive string,

  • -1. Do *not* use `scanf`; it is *notoriously* hard to use correctly. This usage is especially dangerous since it does not restrict input and can easily overflow the `name` buffer. – jamesdlin Feb 07 '19 at 23:44