问题
#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 trailings
should be removed, the syntax for character classes ends with the]
.the number
10
means store at most 10 characters and a null terminator intomystring
. If more than 1 character needs to be stored, the code has undefined behavior.the
printf
conversion specifier forsize_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 ofscanf()
, which must be1
, to avoid undefined behavior in case of invalid input.sizeof(char)
is1
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
withsizeof(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