This document was originally published on www.advogato.org in June 2000 and has been modified based on feedback from that

Making code 64 bit clean

(C) 2000 Dr. David Gilbert - linux @ treblig.org. You may freely copy this document as a whole while keeping this copyright message. You may modify it as long as you keep the copyright notice and state that you have modified it. You may not charge for it.

4th June 2000. Please report any faults, comments or suggestions in this document

As a user of an Alpha processor running Linux I often face the problem of trying to get code to work on it which works fine on the x86 boxes most people are using. Often these programs use constructs which just don't work on 64 bit processors. While this might currently annoy a few nutty Alpha and SPARC users, the IA64 is about to land and mean that 64 bit systems become a bit more common. In this document I try and list some of the common things you have to be careful of to make sure that your code is 64 bit clean.

So what is different?

64 bit Linux machine use an organisation called LP64 where 'long's and pointers are 64 bit in length but everything else is 32 bit; long long's are also 64 bit. 32 bit Linux systems have both long's and pointers as 32 bit and have the 'long long' type as 64 bit.

Pointer lengths

The fact that a pointer is now longer than an int causes probably 80% of the problems encountered. The classic problems are people assigning pointer values to int's and then hoping the value can be converted back into a pointer at the other end. This just won't work on a 64 bit machine. If you need something which can reliably store either an integer or a pointer use a void*. Indeed this is what GNOME does - you'll see it casting int's back and forward to pointers to pass into event routines; when used properly this is safe (if a little unnerving - especially considering the number of compiler warnings that are generated!).

One interesting misuse of a pointer<->int cast I've seen is trying to increment a pointer by an integer offset. So say you are trying to pull apart a protocol and you have a pointer to a structure which you know is at pointer+n bytes where n is an integer. You could find:

struct mystructure* s=(struct mystructure*)(((int)p)+n);
Which works fine on a 32 bit machine. Unfortunatly the int cast is broken on 64 bit machines. A much cleaner solution is to use char*'s:
struct mystructure* s=(struct mystructure*)(((char*)p)+n);
One problem I have seen a few times is where an integer pointer offset is accidentaly made unsigned. So you end up with something like:
void sprang(char* ptr, unsigned int offset) {
  clunk(ptr+offset);
}

void anotherfunc(....) {
  sprang(p,-8);
}

The compiler probably gives a warning here; but it probably stays unnoticed, and on a 32 bit machine due to the wonders of two-s complement arithmetic it does exactly what was expected.

On the 64 bit platform more 'interesting' things take place. The -8 gets converted to a very large 32 bit number and then is added to the pointer which ends up pointing into nomans land and kaboom!

Long abuse

So how long is a long? Well there is no really good definition of that in general! So if you start using a 'long' in place of an 'int' for some reason don't be surprised if it is a different size on a 64 bit platform. So if you are writing values to a hardware register then a 'long' probably isn't a good idea because it will be 64 bits on 64 bit Linux platforms and 32 bit on 32 bit Linux platforms. Other OSs may still keep long at 32 bits on 64 bit systems - so being portable can be difficult.

Misuse of longs can cause lots of problems. The simple ones are things like writing longs to files/sockets for set protocols/file formats. Suddenly you find an incompatibility between 32 and 64 bit machines. So when should you use a 'long' ? Answer - don't unless you are calling an OS or library routine which explicitly requests a long. If you are specifically looking for a 64 bit type then u_int64_t defined in sys/types.h is nice and explicit!

Interacting with varargs functions is fun with long's - consider this printf:

long x;
int y;

printf("The value is %d,%d\n",x,y);

Obviously a %ld was needed - but the lack of it doesn't cause the problems you might expect. Arguments to varargs functions get promoted to 'long' every time - so 'y' will not get corrupted by the incorrect length of 'x'; however 'x' will get printed incorrectly. For pointers always use %p to get your pointers printed correctly irrespective of 64 or 32 bit addresses. The promotion of int's to long's in variable argument lists doesn't help in scanf's of course; so passing an int to a scanf with a %ld is likely to lead to trouble. So care must be taken with long's and the simplest answer is not to use them!

Somethings are less obvious. For a start how do you print out the integer value of a system type (such as id_t) when you don't know if it is long or not? I can't see a truly safe way. Interestingly C++'s overloading solves all these problems rather nicely. The up and coming C99 standard has a special 'large as it gets' integer type and printf format (%j) string to deal with this case. One it does not solve is the problem of how many 0's to pad a pointer to when printing it!

An interesting long abuse is committed in the X Window System; the XChangeProperty function and its friends take a list of items and a parameter specifying the type of the items. This specifier is 8, 16 or 32 however the list of items must be of type long if the specifier is 32 even on 64 bit systems.

Structure sizes

Another issue I'm not proposing a solution to is the sizing of structures. If you are trying to produce a structure of a nice binary size so that an array can be accesssed by shifitng rather than by multiplication you might put some dummy entries in to pad it. e.g.

struct athing {
  int x,y;
  char *next;
  int dummy;
}
On a 32 bit machine this structure would be a nice convenient 16 bytes in length; however on a 64 bit machine it turns out that it is 24 bytes and actually the best thing would be to leave dummy out.

Of select and ffs

The 'select' call returns its findings in an array of long's called fd_set. Many programs use the C library routine 'ffs' to find the first bit set in an element of the set. This is a fast way of finding out which of your file descriptors has come up trumps. Unfortunatly ffs only works on integers, not long's and hence you end up in the situation where if you have more than 32 file descriptors open you start ignoring some. Linux provides 'ffsl' in the glibc to help you with this, but it is not portable and the only sure way is to do it manually.


back to Dave Gilbert's Home Page