Determining presence of prototype with correct return type
Here's a random problem in C black magic I just came up with:
Write a function that returns 1 if malloc
has been prototyped to return a pointer type and 0 if malloc
has a return type of int
(either implicitly or due to wrong prototype), without invoking any implementation-defined or undefined behavior.
I believe it's solvable, but I haven't worked out the solution. Note that calling the function cannot be necessary and in fact is not possible since calling a function with the incorrect prototype is undefined behavior. I have some ideas for ingredients but I think it makes for a better puzzle (and possibly more diver开发者_JS百科se ideas) if I leave them out for now.
I don't need the solution for any immediate use, but it could be handy in configure
scripts and such.
Update: A better example of the usefulness (with strdup
instead of malloc
):
#undef strdup
#define strdup(x) (strdup_has_proto ? strdup((x)) : my_strdup((x)))
i.e. being able to implement X_has_proto
as an expression at the source level could be a portable way to use functions which a system might or might not have, and fall back on a local replacement, without needing any separate configure
step.
There is no solution given the assumptions. What you need is a function whose body has free variables that represent the intermediate type information in the compiler that isn't ever lowered into the target language except in implementation-defined ways, e.g. debugger information. Relax the requirements so that you allow certain prescribed kinds of implementation-defined behavior, and you might find a solution.
Since void *
and int
can be added I was thinking of other operators to try, such as comparison. The results, especially with R's ?:
trick are oddly inconsistent:
int f() { return 0; }
void *g() { return 0; }
#define T(x) (1?0:x()) /* R's trick from a comment */
void h()
{
void *n = 0;
int i = 0;
f() > n; /* m.c:12: warning: comparison between pointer and integer */
g() > n;
T(f) > n;
T(g) > n;
f() > i;
g() > i; /* m.c:18: warning: comparison between pointer and integer */
T(f) > i;
T(g) > i; /* m.c:20: warning: comparison between pointer and integer */
}
Some partial answers and answers to related questions:
If the function in question is supposed to return a floating point type, the test is easy:
#define HAS_PROTO_FP_RET_1_ARG(f) !!((1?1:(f)(0))/2)
The 1 gets promoted to a floating point type if and only if f
is declared to return a floating point type, and division by 2 results in a nonzero value if and only if 1 has floating point type.
This test could be useful for checking for the presence of C99 math functions.
With pointers, the expression (1?0:(f)(0))
is potentially useful - it evaluates to either 0
(an int
) or (void *)0)
(a null pointer constant) depending on the return type of f
. But I have yet to find any devious way to test whether an expression has integer or pointer type.
The big problem I'm running into is that void *
cannot participate in pointer arithmetic and does not implicitly convert to other pointer types in arithmetic contexts. For example, if it did, this would work (slightly breaking my rules about UB/IDB too):
#define HAS_PROTO_PTR_RET_1_ARG(f) ((int)((char (*)[2])2 - (1?0:(f)(0))) == 1)
Any ideas for getting around this problem?
Update: I have a solution that depends on nothing more than intmax_t
being larger than int
:
#define HAS_PROTO_PTR_RET_1_ARG(f) ( \
sizeof(int)!=sizeof(void *) ? sizeof ((f)(0)) == sizeof(void *) : \
sizeof (1?(intmax_t)0:(f)(0)) == sizeof(void *) )
There are two cases. If int
and void *
have different sizes, we simply check the size of the return value. Otherwise, int
and void *
are the same size, so (by the big assumption) intmax_t
and void *
are not the same size. We construct an expression whose type is intmax_t
if the return type is int
, and void *
if the return type is void *
, and test its size.
This macro fails only on machines where int
, intmax_t
, and void *
all have the same size. Practically speaking, that means only on DSPs with 64-bit or larger int
. Since pretty much all real POSIX and POSIX-like systems have fixed-size 32-bit int and intmax_t
is required to be at least 64-bit, this is portable enough to make me happy.
How about some gloriously bad hacks?
char has_malloc = 0;
#define malloc(x) \
has_malloc = 1; \
malloc((x))
//.. run includes here
#undef malloc
But then, you could just #include <malloc.h>
for the guarantee.
Edit: Why not just define an int malloc()? If anyone tries to call an int malloc, it should call your version instead.
精彩评论