55

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?

0

8 Answers 8

98

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;
}
Sign up to request clarification or add additional context in comments.

6 Comments

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?).
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 :-)
@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?
+1 to paxdiablo for elucidating the problem with scanf and gets
@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.
|
22

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;
}

3 Comments

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.
Just updating that getline now requires len to be size_t or unsigned long
Downside: POSIX but not ANSI C.
7

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.)

2 Comments

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.
@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.
4

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).

1 Comment

This should use fgetc instead of getchar so that it uses the supplied stream instead of stdin.
2

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

1 Comment

Can't I have a variable instead of manually entering 99 in scanf(" %99[^\n]", str); ?
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.

Comments

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.

Comments

-2

You can use scanf function to read string

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

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

1 Comment

-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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.