Dynamically allocate user inputted string

末鹿安然 提交于 2019-12-17 14:58:08

问题


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

  • Start an input loop, printing '> ' each iteration.
  • Take whatever the user enters (unknown length) and read it into a character array, dynamically allocating the size of the array if necessary. The user-entered line will end at a newline character.
  • Add a null byte, '\0', to the end of the character array.
  • Loop terminates when the user enters a blank line: '\n'

This is what I've currently written:

void input_loop(){
    char *str = NULL;

    printf("> ");

    while(printf("> ") && scanf("%a[^\n]%*c",&input) == 1){

        /*Add null byte to the end of str*/

        /*Do stuff to input, including traversing until the null byte is reached*/

        free(str);
        str = NULL;
    }
    free(str);
    str = NULL;
}

Now, I'm not too sure how to go about adding the null byte to the end of the string. I was thinking something like this:

last_index = strlen(str);
str[last_index] = '\0';

But I'm not too sure if that would work though. I can't test if it would work because I'm encountering this error when I try to compile my code:

warning: ISO C does not support the 'a' scanf flag [-Wformat=]

So what can I do to make my code work?

EDIT: changing scanf("%a[^\n]%*c",&input) == 1 to scanf("%as[^\n]%*c",&input) == 1 gives me the same error.


回答1:


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.




回答2:


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



回答3:


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.




回答4:


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


来源:https://stackoverflow.com/questions/30271631/dynamically-allocate-user-inputted-string

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!