Skip to content. | Skip to navigation

Navigation

You are here: Home / Support / Guides / Porting / C Integral Types

Personal tools

C Integral Types

The Devil's Own Work!

Of course, any porting guide would start here, and rightly so as C integral types are tricky for the porter.

The compiler is your friend, here, though you might need to prompt it to shout at you when you are abusing integral types, -Wall is a good start. -std=c99 might help too.

You will probably find yourself patching up dreadful assumptions about integral type conversions throughout your code. Enjoy!

Header Files

<inttypes.h> contains macros for printf(3) and scanf(3) conversions, eg. PRId64.

<stdint.h> contains macros for maximum and minimum values for various types, eg. PTRDIFF_MAX.

size_t

We'll start with the easy stuff.

Hah! I lied.

How big is a size_t? Well, judging by interfaces such as strlen(3):

size_t strlen(const char *s);

and the sizeof operator, a size_t would be pretty big, seeing as strings can be pretty big. Right?

Well, there's the rub and what makes size_t probably one of the least understood data types. A size_t is a unsigned type capable of indexing the largest possible array on this system. As a helpful guide, the largest possible index for an array on this system is defined as the macro SIZE_MAX.

Yeah, but SIZE_MAX is (2^32)-1 or (2^64)-1, isn't it? No. You would be very unwise to assume that. Notice that array was highlighted before.

size_t harks back to the days of segmented memory architectures when a segment might mean that the largest possible array had, say, 65536 elements. SIZE_MAX would be 65535 as that would be the largest index into any array (and therefore, say, string) you could have.

So, nothing really to do with pointers, if you were thinking along those lines.

Of course, in practice, a size_t is 32 or 64 bits and there's no worrying about segmented memory architectures and not until you start porting your code do you ever worry about just how big a size_t is. Well, you were warned.

ssize_t

The signed variant of size_t is ssize_t.

Printing size_t

As a side-effect of being such an odd size, there is a specific printf(3) length modifier, z. So %zu for a decimal size_t (remember, it is unsigned) and %zd for a decimal ssize_t (signed).

size_t Uses

size_t is ideal for indexing arrays -- almost like it was designed for that purpose!

SIZE_MAX is the maximum value, as previously noted and 0 is the minimum (in case, like me, you went looking for SIZE_MIN...).

Unlike the following types, you should get a SSIZE_MAX but there isn't a SSIZE_MIN defined.

To keep you on your toes, if you nose around the header files you should find _POSIX_SSIZE_MAX defined to be 32767 often with a comment such as max value of an "ssize_t". Luckily, it doesn't appear to be used by anything as it might cause a bit of a kerfuffle.

size_t Caveats

size_t itself is an unsigned type so if blindly using it for array indexing, you need to be very careful about any descending loops that are likely to reach zero:

size_t i;
for (i = 10 ; i >= 0 ; i--) ...

The problem is that the for loop is allowing i to reach zero and is then decrementing it. Decrementing an unsigned type means it will wrap around to some very large number (greater than or equal to SIZE_MAX). This means it will subsequently pass the i >= 0 test and you will loop forever.

This is usually easy to spot if the code is run regularly but something of a surprise if the code is not run regularly.

ptrdiff_t

The companion type to size_t is ptrdiff_t -- possibly the least helpfully named type as it, nominally, has nothing to do with pointers (at least in modern parlance).

Following the rather specific history of size_t, we have an immediate need to manage the difference between two size_t objects and the type of that difference calculation is ptrdiff_t, a signed type.

There is a subtlety, though, the two size_t objects must be indexes into the same array. All bets are off if they are indexes into two different arrays even if they are in the same memory segment.

On modern systems such memory segmentation subtleties do not exist and ptrdiff_t is used as a signed type for walking over arrays -- if it is used at all.

Printing ptrdiff_t

Another specific printf(3) length modifier, t. Technically, you should only be printing a signed conversion, %td.

ptrdiff_t Uses

Much the same as size_t -- ie. indexing arrays -- with the benefit of being able to go negative.

PTRDIFF_MIN through PTRDIFF_MAX are your limits.

ptrdiff_t Caveats

By definition, a ptrdiff_t should be able to hold the difference between any two size_t indexes (into the same array, etc. etc.) which, given indexes, 0 and SIZE_MAX, could be either +SIZE_MAX or -SIZE_MAX which means ptrdiff_t should be at least one bit larger than a size_t.

Almost certainly it is the same bit width as a size_t (both being 32 or 64 bits) which means that in extremis its use as a "pointer difference" breaks down as you'll get integer wrapping.

If you really can have an index into your array that is (SIZE_MAX/2)+1 or larger then you're on your own for handling differences.

intptr_t

For anyone who is abusing pointers, *cough*, then you're likely to want to use intptr_t and its unsigned sibling, uintptr_t.

These are, surprisingly enough, integral types that can contain a pointer -- however big a pointer is on this system. (Probably 32 or 64 bits.)

Printing intptr_t

The macros PRIcPTR (for c in d, i for intptr_t and o, u, x and X for uintptr_t) are what you want. A decimal value would be printed with:

intptr_t ip = 11;
printf ("v=%" PRIdPTR "\n", ip);

intptr_t Uses

If you are abusing pointers like this then INTPTR_MIN through INTPTR_MAX and 0 through UINTPTR_MAX are your limits.

intmax_t

intmax_t and uintmax_t which, today, are probably 64 bits unless you're on an older operating system.

Note

128 bit arithmetic is possible today with compiler support (although not mainstream enough to reach a C standard yet).

Printing intmax_t

Yet another printf(3) specific length modifier, j. A signed decimal conversion would be %jd.

intmax_t Uses

INTMAX_MIN through INTMAX_MAX and 0 through UINTMAX_MAX are your limits.

intN_t

For managing 8, 16, 32 and 64 bit data types there are specific signed and unsigned integral types, e.g., int64_t with associated printf(3) and scanf(3) macros, e.g., PRId64 and derivative limits, e.g., INT64_MIN through INT64_MAX (and 0 through UINT64_MAX).

time_t

Technically, a time_t is defined as an arithmetic type meaning it could be signed or unsigned or even a double.

It's pretty commonly a signed 32 or 64 bit integer, though. Some systems explicitly code it as an int64_t and others as a long.

For printing, casting to an intmax_t seems to be acceptable:

time_t t = time (...);
printf ("%jd", (intmax_t) t);

off_t

It's not obvious what size an off_t is -- as in whether it is 32 or 64 bits -- and is probably dependent on some compiler settings involving LP64 and ILP32. off_t comes from POSIX and whether off_t == off64_t may depend on whether you've compiled with _FILE_OFFSET_BITS=64.

As a sort of get out of jail free card you can impute that an off_t is going to be the biggest thing you can represent, it is some sort of an integer therefore must be compatible with an intmax_t:

off_t pos = lseek (...);
printf ("%jd", (intmax_t) pos);

Document Actions