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