Dynamically allocate user inputted string

后端 未结 4 1371
不思量自难忘°
不思量自难忘° 2020-12-04 02:47

I am trying to write a function that does the following things:

  • Start an input loop, printing \'> \' each iteration.
  • Take whatever the
相关标签:
4条回答
  • 2020-12-04 03:06

    First of all, scanf format strings do not use regular expressions, so I don't think something close to what you want will work. As for the error you get, according to my trusty manual, the %a conversion flag is for floating point numbers, but it only works on C99 (and your compiler is probably configured for C90)

    But then you have a bigger problem. scanf expects that you pass it a previously allocated empty buffer for it to fill in with the read input. It does not malloc the sctring for you so your attempts at initializing str to NULL and the corresponding frees will not work with scanf.

    The simplest thing you can do is to give up on n arbritrary length strings. Create a large buffer and forbid inputs that are longer than that.

    You can then use the fgets function to populate your buffer. To check if it managed to read the full line, check if your string ends with a "\n".

    char str[256+1];
    while(true){
        printf("> ");
        if(!fgets(str, sizeof str, stdin)){
            //error or end of file
            break;
        }
    
        size_t len = strlen(str);
        if(len + 1 == sizeof str){
            //user typed something too long
            exit(1);
        }
    
        printf("user typed %s", str);
    }
    

    Another alternative is you can use a nonstandard library function. For example, in Linux there is the getline function that reads a full line of input using malloc behind the scenes.

    0 讨论(0)
  • 2020-12-04 03:13

    No error checking, don't forget to free the pointer when you're done with it. If you use this code to read enormous lines, you deserve all the pain it will bring you.

    #include <stdio.h>
    #include <stdlib.h>
    
    char *readInfiniteString() {
        int l = 256;
        char *buf = malloc(l);
        int p = 0;
        char ch;
    
        ch = getchar();
        while(ch != '\n') {
            buf[p++] = ch;
            if (p == l) {
                l += 256;
                buf = realloc(buf, l);
            }
            ch = getchar();
        }
        buf[p] = '\0';
    
        return buf;
    }
    
    int main(int argc, char *argv[]) {
        printf("> ");
        char *buf = readInfiniteString();
        printf("%s\n", buf);
        free(buf);
    }
    
    0 讨论(0)
  • 2020-12-04 03:19

    Some have mentioned that scanf is probably unsuitable for this purpose. I wouldn't suggest using fgets, either. Though it is slightly more suitable, there are problems that seem difficult to avoid, at least at first. Few C programmers manage to use fgets right the first time without reading the fgets manual in full. The parts most people manage to neglect entirely are:

    • what happens when the line is too large, and
    • what happens when EOF or an error is encountered.

    The fgets() function shall read bytes from stream into the array pointed to by s, until n-1 bytes are read, or a is read and transferred to s, or an end-of-file condition is encountered. The string is then terminated with a null byte.

    Upon successful completion, fgets() shall return s. If the stream is at end-of-file, the end-of-file indicator for the stream shall be set and fgets() shall return a null pointer. If a read error occurs, the error indicator for the stream shall be set, fgets() shall return a null pointer...

    I don't feel I need to stress the importance of checking the return value too much, so I won't mention it again. Suffice to say, if your program doesn't check the return value your program won't know when EOF or an error occurs; your program will probably be caught in an infinite loop.

    When no '\n' is present, the remaining bytes of the line are yet to have been read. Thus, fgets will always parse the line at least once, internally. When you introduce extra logic, to check for a '\n', to that, you're parsing the data a second time.

    This allows you to realloc the storage and call fgets again if you want to dynamically resize the storage, or discard the remainder of the line (warning the user of the truncation is a good idea), perhaps using something like fscanf(file, "%*[^\n]");.

    hugomg mentioned using multiplication in the dynamic resize code to avoid quadratic runtime problems. Along this line, it would be a good idea to avoid parsing the same data over and over each iteration (thus introducing further quadratic runtime problems). This can be achieved by storing the number of bytes you've read (and parsed) somewhere. For example:

    char *get_dynamic_line(FILE *f) {
        size_t bytes_read = 0;
        char *bytes = NULL, *temp;
        do {
             size_t alloc_size = bytes_read * 2 + 1;
             temp = realloc(bytes, alloc_size);
             if (temp == NULL) {
                 free(bytes);
                 return NULL;
             }
             bytes = temp;
             temp = fgets(bytes + bytes_read, alloc_size - bytes_read, f); /* Parsing data the first time  */
             bytes_read += strcspn(bytes + bytes_read, "\n");              /* Parsing data the second time */
        } while (temp && bytes[bytes_read] != '\n');
        bytes[bytes_read] = '\0';
        return bytes;
    }
    

    Those who do manage to read the manual and come up with something correct (like this) may soon realise the complexity of an fgets solution is at least twice as poor as the same solution using fgetc. We can avoid parsing data the second time by using fgetc, so using fgetc might seem most appropriate. Alas most C programmers also manage to use fgetc incorrectly when neglecting the fgetc manual.

    The most important detail is to realise that fgetc returns an int, not a char. It may return typically one of 256 distinct values, between 0 and UCHAR_MAX (inclusive). It may otherwise return EOF, meaning there are typically 257 distinct values that fgetc (or consequently, getchar) may return. Trying to store those values into a char or unsigned char results in loss of information, specifically the error modes. (Of course, this typical value of 257 will change if CHAR_BIT is greater than 8, and consequently UCHAR_MAX is greater than 255)

    char *get_dynamic_line(FILE *f) {
        size_t bytes_read = 0;
        char *bytes = NULL;
        do {
             if ((bytes_read & (bytes_read + 1)) == 0) {
                 void *temp = realloc(bytes, bytes_read * 2 + 1);
                 if (temp == NULL) {
                     free(bytes);
                     return NULL;
                 }
                 bytes = temp;
             }
    
             int c = fgetc(f);
             bytes[bytes_read] = c >= 0 && c != '\n'
                                 ? c
                                 : '\0';
        } while (bytes[bytes_read++]);
        return bytes;
    }
    
    0 讨论(0)
  • 2020-12-04 03:24

    If you are on a POSIX system such as Linux, you should have access to getline. It can be made to behave like fgets, but if you start with a null pointer and a zero length, it will take care of memory allocation for you.

    You can use in in a loop like this:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>    // for strcmp
    
    int main(void)
    {
        char *line = NULL;
        size_t nline = 0;
    
        for (;;) {
            ptrdiff_t n;
    
            printf("> ");
    
            // read line, allocating as necessary
            n = getline(&line, &nline, stdin);
            if (n < 0) break;
    
            // remove trailing newline
            if (n && line[n - 1] == '\n') line[n - 1] = '\0';
    
            // do stuff
            printf("'%s'\n", line);
            if (strcmp("quit", line) == 0) break;
        }
    
        free(line);
        printf("\nBye\n");
    
        return 0;
    }
    

    The passed pointer and the length value must be consistent, so that getline can reallocate memory as required. (That means that you shouldn't change nline or the pointer line in the loop.) If the line fits, the same buffer is used in each pass through the loop, so that you have to free the line string only once, when you're done reading.

    0 讨论(0)
提交回复
热议问题