Marc Aurele La France wrote:
> On Sat, 13 Apr 2002, Wojtek Lerch wrote:
> > Marc Aurele La France <yyy@xxxxxxxxxxx> said:
...
> > > *(void **)(&fptr) = dlsym(handle, "my_function");
>
> > > ... which is, I'll grant, a little kludgy, but is perfectly legal in
> > > ANSI or ISO C terms.
>
> > A little?
>
> > It's only "legal" in the sense that it doesn't require the compiler to issue
> > a diagnostic; but it is undefined behaviour in ISO C. And it's actually
>less
> > portable than the original typecast:
>
> > * On a platform where function pointers are smaller than data pointers,
> > you're clobbering memory outside of the fptr variable. A cast from void* to
> > a function pointer would just work: yes, it would throw away the extra bits
> > instead of storing them in some unrelated variable, but if the symbol you
> > asked for indeed was a function, those extra bits would have all been
> > insignificant anyway.
>
> > * Even if function pointers are the same size as data pointers, their
> > representation could be different. Imagine a machine that stores function
> > pointers as "big endian" (segment, then offset) but data pointers as "little
> > endian" (offset, then segment). A normal typecast would allow the compiler
> > to swap the segment and the offset; yours doesn't.
>
> Both objections are irrelevant. My suggestion was given within the
> context of the current specification for dlsym(). Given dlsym() is
> currently constrained to return a void *, it has no portable means
It doesn't need portable means. Implementation-defined means are
sufficient. The implementation of dlsym() is not required to be
portable.
> returning to its caller any information about a symbol other than its
> existence/non-existence. Thus, any portable dlsym() implementation
> requires that all pointers have exactly the same representation.
I'm not sure what you mean by "portable dlsym() implementation"; my
point was that with some implementations of dlsym(), your code can
produce bad results while the original one would work. If you claim
that it's impossible to implement dlsym() on the two platforms that I
described, then I disagree. If you just chose to ignore the possibility
of ever having to run your code on such platforms because dlsym() cannot
have a "portable implementation" on them (whatever that means to you),
then I guess we don't disagree.
The only real requirement that current specification for dlsym() imposes
on implementations is that for any symbol (or at least any symbol that
can potentially be the result of dlsym()), a void* value can be
generated that turns into the pointer to the symbol when converted to
the symbol's type.
For data objects, the existence of such a void* pointer is already
guaranteed by the C standard: converting a valid object pointer to void*
must produce a value that points to the same object when converted back
to the original type. Notice that this is *not* equivalent to requiring
that all data pointers must have the same representation. They don't
have to. On some implementations, int pointers have fewer bits than
void pointers, and that doesn't make implementing dlsym() impossible,
just like it doesn't make implementing malloc() impossible.
For functions, the C standard doesn't give a similar guarantee. Trying
to convert a void* to a function pointer is undefined behaviour in
standard C. But nothing prevents implementations from defining this
behaviour -- and nothing prevents POSIX from requiring its
implementations to require a particular behaviour where the C standard
leaves the behaviour undefined.
That's what the case is with dlsym(): POSIX does require that the void*
value returned by dlsym() can be converted to the appropriate function
pointer type, producing a valid pointer that points to the requested
function. It's true that this requirement restricts the set of possible
compilers that can be used to implement POSIX -- in that sense, it makes
possible implementations of dlsym() less "portable". POSIX (or, more
accurately, the option of POSIX that contains dlsym()) can only be
implemented using compilers that define the result of converting certain
void pointers to function pointers.
But again, POSIX doesn't say anywhere that this kind of a conversion
cannot change the representation. The only problem is that on a machine
whose "natural" size of function pointers is bigger than the "natural"
size of data pointers, this requirement may be impossible to satisfy
without making void pointers bigger then their "natural" size, which
sounds like a big price to pay for little gain. Changing the type that
dlsym() returns to an "implementation-defined" pointer or integer type
("dlsym_t"?) would be a *very simple* way of avoiding this problem.
Splitting dlsym() into two functions would be a more elegant way, but it
might break some existing code more seriously. Personally, I think that
doing both is the best solution.
|