Assuming a typical modern operating system, your program 1 does not crash because calloc
had to request an entire page (4096 bytes of RAM, usually) from the OS to satisfy the request for 10 bytes. If you feed that program sufficiently many characters, it will crash. However, writing even one byte more than the overtly requested size (10 bytes) is forbidden, and has an excellent chance of corrupting the internal data structure used to keep track of "heap" allocations. It is probable that if you added another malloc
or free
call to this program, after the scanf
, it would crash inside that malloc
or free
. By way of illustration, consider this program:
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *p = malloc(23);
memcpy(p, "abcdefghijklmnopqrstuvwx", 25);
char *q = malloc(1);
return 0;
}
➽
$ MALLOC_CHECK_=1 ./a.out
*** Error in `./a.out': malloc: top chunk is corrupt: 0x0000000001bc4020 ***
(On this system, copying only 24 bytes does not crash. Do not rely on this information.)
Program 2, meanwhile, is probably crashing not because the scanf
call wrote all the way to unmapped memory (which, for similar reasons, would require far more bytes of input) but because data on the stack is very densely packed and it clobbered something critical, e.g. the address to which main
should return.
In a program that does anything even a little more complicated than your examples, both "techniques" are equally dangerous -- both heap and stack overflows can and have lead to catastrophic security holes.
You explicitly asked for a comparison between your two unsafe techniques, but for the benefit of future readers I am going to describe two much better techniques for reading strings from standard input. If your C library includes it, the best option is getline
, which (in a simple program like this) would be used like so:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *line = 0;
size_t n = 0;
ssize_t r;
fputs("Enter a string: ", stdout);
fflush(stdout);
r = getline(&line, &n, stdin);
if (r == -1) {
perror("getline");
return 1;
}
if (r > 0 && line[r-1] == '\n')
line[r-1] = '\0';
printf("You entered %s\n", line);
free(line);
return 0;
}
If you don't have getline
, and you need to read an arbitrarily long string from the user, your best option is to implement getline
yourself (gnulib has an implementation you can borrow, if your code can be released under the GPL). But an acceptable alternative in many cases is to place an upper limit on input length, at which point you can use fgets
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LINE_LEN 81
int main(void)
{
char *line = malloc(MAX_LINE_LEN);
size_t n;
fputs("Enter a string: ", stdout);
fflush(stdout);
if (!fgets(line, MAX_LINE_LEN, stdin)) {
perror("fgets");
return 1;
}
n = strlen(line);
if (line[n] != '\n') {
fprintf(stderr, "string too long - %u characters max\n", MAX_LINE_LEN);
return 1;
}
line[n] = '\0';
printf("You entered %s\n", line);
free(line);
return 0;
}
Notes:
sizeof(char) == 1
by definition; therefore, sizeof(char)
should never appear in well-written code. If you want to use calloc
to allocate a prezeroed array of characters, write calloc(1, nchars)
.
- Never use
scanf
, fscanf
, or sscanf
.
- Do not confuse
fgets
with gets
. fgets
is safe if used correctly; it is impossible to use gets
safely.