I was trying to induce an ENOMEM error just to satisfy my own curiosity, so I decided to run the following code:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
double POS_INF = 1.0/0.0;
char *s = malloc(POS_INF);
}
To my suprise, it compiled and ran successfully. Shouldn't there have been an ENOMEM error or some other type of error?
I decided to run valgrind to see what was going on. On the sixth line of the valgrind output, it showed the following:
Argument 'size' of function malloc has a fishy (possibly negative) value: -9223372036854775808
This output really confuses me. Does it think that the POS_INF
variable, which is meant to be equal to positive infinity, is actually -9223372036854775808
?
Any help on this would be highly appreciated.
CodePudding user response:
Function malloc()
as defined in <stdlib.h>
takes an argument of type size_t
. Passing a double
invokes an implicit conversion from double
to size_t
, which is only defined if double
value is finite and the integral part in within the range of type size_t
.
Converting Infinity
is hence undefined. Note that computing 1.0/0.0
is not defined on all platforms, but does evaluate to a positive infinity on IEEE-754 conforming architectures such as most modern systems.
As a consequence, your code has undefined behavior. It seems converting a positive infinite value (if your system supports it) to the type unsigned long
for which size_t
is a typedef on your system produces the value 0x8000000000000000
(9223372036854775808
in decimal, reported as -9223372036854775808
if converted as a signed value), which malloc()
reports as fishy... I tend to agree :). Other systems might behave differently, possibly terminating your program abruptly.
Malloc detects this argument value as fishy because programmers passing negative values to malloc()
actually pass huge unsigned values as converted implicitly to unsigned type size_t
. This explains the error message and the signed conversion. It must be a common enough mistake to warrant issuing a runtime warning to stderr
, but it leaves the user clueless about something of interest only to the programmer.
Here is a modified version:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
double POS_INF = 1.0/0.0;
char *s = malloc(POS_INF);
printf("(size_t)%g -> %zu\n", POS_INF, (size_t)POS_INF);
printf("malloc(%zu) -> %p\n", (size_t)POS_INF, (void *)s);
if (s == NULL) {
printf("errno=%d %s (%s)\n", errno,
errno == ENOMEM ? "(ENOMEM)" : "",
strerror(errno));
}
free(s);
return 0;
}
Running this program multiple times gives a good illustration of undefined behavior: the conversion gives different values in successive occurrences (but produces a value small enough for malloc()
to allocate):
chqrlie$ make 220929-malloc
clang -O3 -Weverything -Wno-padded -o 220929-malloc 220929-malloc.c
220929-malloc.c:8:22: warning: implicit conversion turns floating-point number into integer: 'double' to 'unsigned long'
[-Wfloat-conversion]
char *s = malloc(POS_INF);
~~~~~~ ^~~~~~~
1 warning generated.
chqrlie$ ./220929-malloc
(size_t)inf -> 4194304
malloc(3520768) -> 0x7fbc69400350
CodePudding user response:
How come it's possible to malloc() an infinite amount of memory in C?
It is not possible to malloc
an infinite amount of memory in C.
Firstly, remember that programming in general (and C in particular) is not pure mathematics. The concept of "infinity" does not necessarily even exist.
It's true, IEEE-754 has a distinct and meaningful value for "infinity". (It even has two: positive and negative.) And, it's also true, the expression 1.0/0.0
will generate this value.
However, malloc
does not take a double. So the concept, "malloc
an infinite amount of memory" is quiet meaningless. malloc
actually takes an argument of type size_t
, so the only values you can (attempt to) malloc
are values that can be expressed as type size_t
.
Now, C supports implicit conversions, including from double
to size_t
, so the compiler will attempt to do that. But I don't think the conversion of double
to size_t
is perfectly defined (or can be). I can't remember, but I'm pretty sure the rule is that if the truncated double
value can't be properly represented as a value of type size_t
, the result is undefined. So malloc(1024.0)
is fine, and malloc(1024.1)
is probably also fine, but malloc(1e100)
is not fine, and malloc(1.0/0.0)
is right out.
And then, finally, you expressed surprise that malloc
didn't fail, but in fact, maybe it did: since your program didn't check, you have no way of knowing!
The last line of your test program was
char *s = malloc(POS_INF);
So you assign malloc
's result to a pointer variable s
, but then throw it away and exit. Since you don't do anything with s
, it wouldn't be too wrong for the compiler to throw away the whole line — that is, to not call malloc
at all — since you obviously didn't care about the return value, and would have no way of knowing if the compiler cheated and didn't call it.
Suppose you add the lines
if(s == NULL) {
perror("malloc");
exit(1);
}
at the end of the program. Now you're actually in a position to determine whether malloc
failed.
After that there are three possibilities:
malloc
fails, because the convertedsize_t
value (whatver it is) asks for more memory than is available.malloc
succeeds, because the convertedsize_t
value (whatver it is) asks for memory that is available.malloc
seems to succeed, because although the convertedsize_t
value asks for more memory than is available, you're on a system that overcommits memory, and only fails when you try to use it.
So to really see what's going on, you might want to change the code to
size_t sz = POS_INF;
char *s = malloc(sz);
if(s == NULL) {
printf("can't malloc %zu bytes: %s\n", sz, strerror(errno));
exit(1);
}
so that you can actually see what size_t
value you got. And to be really sure, you can follow that up with
s[0] = 1;
s[sz/2] = 2;
s[sz-1] = 3;
to see if you actually got memory you can use.
CodePudding user response:
First thing, in case memory allocation functions (malloc()
and family) fails to allocate the memory, they return a null pointer. They don't create some fault or error on their own. You need to check for the validity (non-null pointer in this case) after the call to a(ny) library function as a best practice.
That said, malloc()
accepts an argument of type size_t
. Passing a double is not allowed.