Portable C and long

The C standard orginally defined three sizes of integer (apart from char): short, int and long. It was stated that int should reflect the natural size for a particular machine. ANSI C added the requirement that short and int be at least 16 bits, and long at least 32 bits.

One might therefore assume that on any 64 bit OS, int would be 64 bits. One would be wrong. Today int is almost universally 32 bits, save on some embedded systems on which 16 bit ints might be found.

But then presumably long will be 64 bits on any 64 bit system? It is on MacOS, Linux and all UNIX derivatives I am aware of, but it is not on MS Windows. There it is 32 bits still, even on 64 bit platforms, just as it was on 32 bit Windows, and most 32 bit UNIXes.

The 1999 C standard added another integer data type, long long. This had been in widespread use as an extension to the 1989 standard, and the C99 standard specified that it is at least 64 bits.

So amongst common 64 bit OSes, there are two different implementations of the sizes of int, long and long long. UNIX-based systems tend to use length of 4/8/8 (in bytes, as returned by sizeof()), whereas Windows uses 4/4/8. In a different terminology, 4/8/8 is called LP64 (long and pointers 64 bit) and 4/4/8 is LLP64 (long long and pointers 64 bit).

Sometimes one sees ILP64, i.e. 8/8/8. This is most common in numerical libraries to allow arguments which are historically specified as default integers to exceed 231. Even those avoiding C and using Fortran are still caught, as most Fortran compilers default to having Fortran's integer type default to being identical to C's int, and thus being 32 bits. Common numerical libraries, such as BLAS, LAPACK and FFT libraries, are very likely to expect 32 bit integers in their arguments.

To add to the confusion, I believe that the Cygwin environment on 64 bit Windows is LP64, just as most UNIXes are.

Hardware support for 128 bit integers is common. As one example, the 32 bit Pentium III introduced it to the IA32 range in 1999, and all IA32 and x86_64 CPUs since have had it (the `SSE' extensions). No C compiler I am aware of has long long as 128 bit though.

C99 did introduce the int32_t and int64_t types. These were marked as optional, but int_least32_t and int_least64_t are required (smallest integer with at least N bits).

When it matters


The standard C library functions fseek() and ftell() use the type long for the file offset. So code using these functions will be unable to cope with files of more than 2GB on Windows platforms.

Switching to using fsetpos() and fgetpos() may not be the answer. These functions return a fpos_t type, which may well avoid the 2GB limit, but they are not guaranteed to return an integer. The only thing one can do with the return value of fgetpos() is pass it to fsetpos(). One cannot modify it, so one can save one's position, but not seek to somewhere new.

Switching to using fseeko() and ftello() may not be the answer. These functions return an offset_t type, which may well avoid the 2GB limit, but they are not in any C standard. They are part of the POSIX standard of 2001, but Windows tends not to support POSIX.

Switching to non-standard extensions such as _fseeki64() and _ftelli64() is never good for portability. This particular pair exist on Windows, but not elsewhere.

Neither C99 nor C11 introduces any new functions to help here.


Trying to print pointers or file offsets by casting them to long and using a format specifier of %ld will not work well on an LLP64 system. Since C99 one can cast to intmax_t and format with %jd.