Fog Creek Software
Discussion Board




C question: pointers to longs?

I discovered something most interesting in a clients code today: they have some C callbacks, and instead of the usual void* as the 'user data' argument, they have an unsigned long. Which they cast to function pointers and any number of other things. Is this valid? I mean, I know it works, because this is production code, but is this at all portable? I haven't been able to find information on C's guarantees regarding pointer types.

Gah, ugly code. Really, it really is.

Anon to protect the guilty
Monday, February 09, 2004

There are no guarantees, so no, it's not portable.

Brad Wilson (dotnetguy.techieswithcats.com)
Monday, February 09, 2004

All code is ugly except to the original programmer who only understands what he wrote at the time it was written.

...anywho... don't know about your pointer situation, what compiler are you using, what platform are you compiling it on and what is the name your pet fish?


Monday, February 09, 2004

Ah, the good old mad dash to feel superior (justifying your existence) by proclaiming all other code to be inferior on whatever trivial context you can find. "But...what if you try to port this to a Itanium!!!!!  What if..."

.
Monday, February 09, 2004

The usual platforms are OSX, Windows (VS.NET), and Solaris (I don't remember what version). It seems that they shipped it like that, but damn...

Anyway, some code is ugly to everyone, especially if you can't trust the compiler to do what you want.

And the fish's name is Wanda.

Anon to protect the guilty
Monday, February 09, 2004

This works as long as sizeof(void *) = sizeof(long).  This
is almost always true on 32 bit machines.  Once you go
to 64 bit (or 16 bit) machines, this will probably break.
Also, note that function pointers are not always the same
size as data pointers - I just finished a project on
a processor where function pointers were 3 bytes long,
while data pointers were 2 bytes long.

So, this isn't portable.  But if it satisfies the requirements,
it's good code :)

x
Monday, February 09, 2004

Just out of curiousity C people -- any idea why the original programmer used the long syntax in the first place? Assuming the void* is more portable, what is the advantage to passing the long?

Aaron Boodman
Tuesday, February 10, 2004

That long thing is pretty common. I've seen it in a lot of code, including I think parts of the Apple ToolBox API.

It's not portable and is poor style but what can ya do.

Dennis Atkins
Tuesday, February 10, 2004

The reason for it by the way is there will be some sort of auxillary parameter that a callback function can take. Maybe it is a flag, or maybe it is a count, or maybe, just maybe, it is a pointer to a data structure! So they just make it a long and say it can be all of these things or none of them or some of them... sigh.

Dennis Atkins
Tuesday, February 10, 2004

One possibility is the original code is using a generic
function manager to manage functions and pass
dynamically determined argument types.  I've used this
style many times; the biggest gotcha is that either everything
must be a pointer of the same size or be a datatype that
is equal to the size of a pointer.  Also, lots of people
aren't aware that while sizeof(void) doesn't make sense,
sizeof(void *) definitely does.

x
Tuesday, February 10, 2004

Neat. Thanks for the info; very interesting. Seems like a fast and loose equivalent to passing Object to a callback in the managed world.

Never let it be said that C programmers aren't ballsy. I'm scared just thinking of it.

Aaron Boodman
Tuesday, February 10, 2004

> any idea why the original programmer used the long syntax in the first place?

Perhaps they learned the Windows API, where it's very common: in a great number of the Windows messages, the "LPARAM" represents a pointer to something.

Christopher Wells
Tuesday, February 10, 2004

LPARAM or when you use SetWindowLong() or SetProp ().
That's why they have now SetWindowLongPtr(), but I don't
think you would find SetWindowLongPtr() mentioned in
Petzold.
So anyone who is not paranoid about certain things
would mix longs with pointers.

Just to protect yourself, put a check at startup and exit
from app if (sizeof(long) != sizeof (void *)).

VPC
Tuesday, February 10, 2004

I've definitely worked on older hardware where sizeof(void*) != sizeof(long). That is definitely the case for Motorola 60800 series and Western Digital processors, the first systems that I learned C on. Even though I don't like using a long as a pointer, I don't see any point in retrofitting working code to support hardware that shouldn't be in production.

Clay Dowling
Tuesday, February 10, 2004

> Assuming the void* is more portable, what is the advantage to passing the long?

I had to give it a second thought.

As far as I've heard, converting from Win16 to Win32 was
messy and hard no matter what where your programming
practices.

In 32 bit world, pointer or long are really the same thing.
If you have a 32 bit value, you can use it as an address or
as a loop counter or whatever.

On source code level it's some sort of a hack. You can
dislike it, but here and there you'll use it even when you
don't know, like when you interact with some API.

Just look for example to any resource related stuff, namely
your WinMain(). Chances are that you have a line like
this:

wndclass.hIcon = LoadIcon (hIns, MAKEINTRESOURCE(APP_ICON));

LoadIcon() expects there some string address, but you
are sending long integer. It's a hack as I see it, but it works.

So all you can do about these things is to isolate them
with macros or inline functions and avoid having
casts like this:
void *somePtr = (void *) params->longValue;

For amusement, load header file Winbase.h into editor
and you'll find this macro:

#define MAKEINTATOM(i) (LPTSTR)((DWORD)((WORD)(i)))

It's even worse than making pointers from long values.

People usually do these things when they have to add
new features but they can't add new parameters
because of compatibility reasons. And you end up using
params you already have in most creative way you can.

Apple had strange hack in their GraphPort or similar
record. If one special field was negative, other two 16 bit
fields combined into one 32bit value would give you a
pointer to some additional data.

And it worked! ;)

VPC
Tuesday, February 10, 2004

The reason for MAKEINTRESOURCE/MAKEATOMINT that converts an integer to a string relies on the fact that a string allocated in Win32 will have an address greater than 0xFFFF.  WinApi Code that relies on this actually does a check of the value to make sure it is less than 0xFFFF and if it is, it looks up the string value in the string table (resouce file) or the atom in the atom table.  If on the off chance you pass a word greater than 0xFFFF you will more than likely GPF the function (unless of course you get lucky and point to some useable memory)

This is more of a trick to pass an integer representation of a string.  Different than using a long to point to a block of memory (void *).  The integer value is not pointing/referencing any 'real' memory.  Many times you will see a HANDLE like HWND defined as a ULONG.

This was all very common in the good old days of c.  I may concede the MAKEINTRESOURCE handle as a 'hack' but I would not necessarily classify all implementations of using a long (or handle) as hack... 

It's all in what you are used to/where you come from.

Mike

MikeG
Tuesday, February 10, 2004

Even though dynamic function pointers may seem wierd,
you can actually handle them in a portable way - you just
have to have both a generic function pointer and specific
function pointers for each kind of function you want to call.

For example, you have a generic function ptr

typedef void *(*func_ptr ) (void *, ...);

typedef struct dyn_func {
    int func_type;
    func_ptr func;
} dyn_func;

dyn_func funcs[10];

Now, to safely call a func, you need a specific declaration
for its type, to guarantee portability, for example:

typedef int (*func_type_5) (char, int);

Now assume funcs[5] is a function whose proto is
described above.  To call it safely and portably, do

int retval = ((func_type_5) (funcs[5].func)) ('x', 4);

The cast alerts the compiler to set up the calling
correctly so that the arguments are passed correctly,
even if the compiler optimizes calling stacks.  Most
PC compilers don't do this, but most microcontroller
compilers do.

THis isn't _truly_ dynamic, but it allows
a high degree of dynamism.  If you want true dynamism,
you have to constrain things - typically, you can only
allow pointer arguments (to datatypes which may be
opaque and managed by the user) and a number of
arguments known at compile-time.

x
Tuesday, February 10, 2004

> This is more of a trick to pass an integer representation of a string.

Trick or hack! Isn't that the same?

VPC
Tuesday, February 10, 2004

x, I am not sure that I quite get what you've said.

Can you give an example when this could be usefull?

If you created a function to expect void * as first
param (and declare it that way), you can trick compiler
to let you pass it single char, but how would your proc
detect what was recieved as first param?

You've got to have some condition met where you can
realise within such function that you have to view void *
param as something else.

VPC
Tuesday, February 10, 2004

You have a function

int foo(char c, int y)

which is of "magic type 5". 

You assign it to the array by doing

funcs[5].func = (func_ptr) foo;

The definition of func_ptr is irrelevant as long as it creates
a pointer of the right size.  I don't want a "void *" since
func ptrs can be a different size than a data pointer on
some platforms.

You then call it by doing

foo_return = ((func_type_5) (dyn_funcs[5].func)) ('x', 4);

The context in which I've used this sort of thing is when
writing interpreters for scripting languages, where you
want to have an easy-to-extend type system.  You have all
the operators and language builtins in a data structure,
and execute operators and such by calling a dynamic
function manager with the function that implements
the operator and the operator arguments.

All you need to do to add an new type to the language is
to write the functions that implement the type's operators
and such, and add the function declarations to this
master structure containing the function itself, a description
of the types of its arguments, and its return value.  If it's
done right, a new type can be added without having
to touch the expression evaluation code that uses this
structure.  We even allow the user to add types to the
system on the fly at runtime using this scheme.

(And before anyone asks - the reason this isn't in C++ is
because it has to run on very constrained embedded
platforms.  The smallest we have to support is 32K RAM,
16K ROM, 16K Flash, on an 8 Mhz processor.)

x
Tuesday, February 10, 2004

*  Recent Topics

*  Fog Creek Home