Eric Sosman said:
It depends on what you think "it" is. If "it" is the
original code fragment
... then there's no "implicit int" in play at the point of
the call. The call uses a function pointer whose type almost
matches that of malloc() -- the only discrepancy being the
omission of the prototype -- so all is well unless, as I
mentioned, size_t is promotable. (I overlooked something:
there's also trouble if, for example, TWELVE_MILLION is an
int or double or some such, because there's no prototype to
force its conversion to size_t.)
In the following, assume that there's no visible prototype for
malloc().
Suppose different pointer-to-function types are represented
differently. The standard guarantees that any pointer-to-function
type can be converted to any other pointer-to-function type, but the
compiler has to know the type of the original pointer in order to
generate correct code for the conversion.
In the above code fragment, the compiler doesn't necessarily know that
the malloc() function returns void*. (It's allowed to know this,
since it's in the standard library, but it's not required to.) The
compiler sees the identifier "malloc", and it knows that it's the name
of a function, but it doesn't know anything else about it; in
particular, it doesn't know the function's return type. One
reasonable thing for the compiler to do is to assume that the function
returns int (I'm not sure whether this is required, but it's certainly
allowed). So it generates code to convert something of type
"int (*)()" to type "void *(*)()", and applies this conversion to the
value of malloc. But malloc is really of type ""void *(*)()" already
(we've just hidden this fact from the compiler), so the conversion
yields garbage, and the attempt to do a function call using this
garbage pointer-to-function value invokes undefined behavior.
This is the same problem we see with the more common
int *ptr = (int*)malloc(sizeof int);
except that we're lying to the compiler about the type of the malloc
function rather than lying to the compiler about the type of the
result returned by the malloc function. In either case, we're
covering up the lie by doing a conversion, but we're not telling the
compiler what to convert from.
Ignoring the problem with the TWELVE_MILLION argument, this is all
likely to "work" either if all pointer-to-function types have the same
representation (which is probably true for all actual implementations)
*or* if the compiler uses its knowledge of the actual type of
malloc(). That just means that the undefined behavior will rarely, if
ever, cause any visible problems.
You can't meaningfully use malloc unless the compiler knows its type,
and the only way to tell the compiler what malloc's type is is to
Let's try a variation, shall we? In one file we'll have
#include <stdlib.h>
typedef void * (*fptr)(size_t); /* for readability */
fptr get_malloc_ptr(void) {
return malloc;
}
This function just returns a pointer to malloc(), which in turn
is properly declared by the header. Now in a separately-compiled
file we'll have
#include <stddef.h> /* for size_t */
void *my_malloc(size_t bytes) {
void * (*f)(size_t) = get_malloc_ptr();
return f(bytes);
}
This function acquires a pointer to malloc(), calls via the pointer,
and returns the result. Note carefully that no malloc() prototype
is visible at the point of the call -- there isn't a declaration
of any kind visible, with or without a prototype. Yet there is no
error here, no undefined behavior, no contravention of anything
except good sense.
If the second code fragment has a visible prototype for
get_malloc_ptr(), this should work correctly without invoking
undefined behavior. The compiler knows the type of the result of
get_malloc_ptr() because you've declared it properly. You haven't
lied to the compiler.
(If there is no visible prototype for get_malloc_ptr(), the compiler
will assume it returns int; the attempt to initialize f, which is of
type void *(*)(size_t), with an expression of type int is a constraint
violation.)
The original code does pretty much the same thing, except
that it uses a different way of acquiring the pointer to malloc().
Both examples make their actual call to malloc() via a function
pointer expression of the proper type.
No, the original code does a conversion of a pointer-to-function value
without knowing the actual type of the value being converted.
[...]