How to save the scanf input only if there's enough space in the array? How to reallocate array to let the scanf input fits in?

时间秒杀一切 提交于 2020-06-17 06:30:27

问题


#include <stdio.h>

int main() {

    char *mystring = calloc(2, sizeof(char));

    scanf("%10[^\n]s", mystring);

    printf("\nValue: %s\nSize of array: %d\nAllocated space: %d\n",
           mystring, 2 * sizeof(char), sizeof(char) * strlen(mystring));

    free(mystring);
}

Output:

$ ./"dyn_mem" 
laaaaaaaaaaa

Value: laaaaaaaaa
Size of array: 2
Allocated space: 10

This code can produce an undefined behavior if I enter in the scanf input a string bigger than array size. How can I handle this ?


回答1:


There are multiple problems in your code:

  • mystring is initialized to point to an allocated block of 2 bytes. Technically, you should test for memory allocation failure.

  • the conversion format "%10[^\n]s" is incorrect: the trailing s should be removed, the syntax for character classes ends with the ].

  • the number 10 means store at most 10 characters and a null terminator into mystring. If more than 1 character needs to be stored, the code has undefined behavior.

  • the printf conversion specifier for size_t is %zu, not %d. If your C library is C99 compliant, use %zu, otherwise case the last 2 arguments as (int).

  • the sizes output do not correspond to the labels: the first is the allocated size, and the second is the length of the string.

  • the scanf() will fail if the file is empty or starts with a newline. You should test the return value of scanf(), which must be 1, to avoid undefined behavior in case of invalid input.

  • sizeof(char) is 1 by definition.

There are many ways to achieve your goal:

On systems that support it, such as linux with the GNU lib C, you could use an m prefix between the % and the [ in the scanf() conversion format and pass the address of a char * as an argument. scanf() will allocate an array with malloc() large enough to receive the converted input.

Here is a modified version for linux:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *mystring = NULL;
    if (scanf("%m[^\n]", &mystring) == 1) {
        printf("Value: %s\n"
               "Length of string: %zu\n"
               "Allocated space: %zu\n",
               mystring, strlen(mystring), malloc_usable_size(mystring));
        free(mystring);
    }
    return 0;
}

On POSIX systems, you could use getline() that reads a line into an allocated array.

On other systems, you would need to write a function that reads the input stream and reallocates the destination array as long as you don't get a newline or the end of file.

A common compromise is to make an assumption about the maximum length of the input:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char buf[1024];
    if (scanf("%1023[^\n]", buf) == 1) {
        char *mystring = strdup(buf);
        if (mystring) {
            printf("Value: %s\n"
                   "Length of string: %d\n",
                   "Minimum allocated size: %d\n",
                   mystring, (int)strlen(mystring), (int)strlen(mystring) + 1);
            free(mystring);
        }
    }
    return 0;
}

You could also use fgets() to read a line from the input stream and strip the newline (if any). This approach has the advantage of not failing on empty lines.

Here is a simple implementation of getline() that should fit your needs:

#include <stdio.h>
#include <stdlib.h>

int my_getline(char **lineptr, size_t *n, FILE *stream) {
    char *ptr = *lineptr;
    size_t size = *n;
    size_t pos = 0;
    int c;
    while ((c = getc(stream) && c != '\n') {
        if (pos + 1 >= size) {
            /* reallocate the array increasing size by the golden ratio */
            size = size + (size / 2) + (size / 8) + 16;
            ptr = realloc(ptr);
            if (ptr == NULL) {
                ungetc(c, stream);
                return EOF;
            }
            *n = size;
            *lineptr = ptr;
        }
        ptr[pos++] = c;
        ptr[pos] = '\0';
    }
    return (int)pos;
}

int main() {
    char *mystring = NULL;  // must be initialized
    size_t size = 0;        // must be initialized
    int res;

    while ((res = my_getline(&mystring, &size, stdin)) >= 0) {
        printf("Value: %s\n"
               "Length of string: %d\n",
               "Allocated size: %d\n",
               mystring, res, (int)size);
    }
    free(mystring);
    return 0;
}



回答2:


Option #1

from Kernighan and Ritchie 2nd ed appendix B.1.4

char *fgets(char *s, int n, FILE *stream)

fgets reads at most the next n-1 characters into the array s, stopping if a newline is encountered; the newline is included in the array, which is terminated by '\0'. fgets returns s, or NULL if end of file or error occurs.

replace n with sizeof(char)*strlen(mystring) in your code

Option #2

also from Kernighan and Ritchie 2nd ed appendix B.1.4

int fgetc(FILE *stream)

fgetc returns the next character of stream as an unsigned char (converted to an int), or EOF if end of file or error occurs.

and manually put in a for loop with sizeof(char)*strlen(mystring) as the limit




回答3:


This code can produce an undefined behavior if I enter in the scanf input a string bigger than array size.

Yes.

How can I "handle" this ?

By ensuring that you always pass scanf a pointer to an object of type appropriate for the corresponding conversion directive. hat is always your responsibility as a C programmer. For s and [ directives, "appropriate" includes being large enough to accommodate all possible converted values.

It is easy enough to do that when the format expresses the maximum size of the input, either directly, as in the example, or parametrically. And the format is under your control. But if you need to handle input of unbounded size then scanf isn't up to the task, at least not by itself. In that case, you need to implement a variation on guessing how much space you'll need, and acquiring more if that turns out not to be enough. Among other things, that means being prepared to read the input in more than one piece, and probably obtaining space for it by dynamic allocation.



来源:https://stackoverflow.com/questions/61877680/how-to-save-the-scanf-input-only-if-theres-enough-space-in-the-array-how-to-re

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