Sun Studio 12: C User's Guide

7.2 Implementing Single Source Code

The following sections describe some of the available resources that you can use to write single-source code that supports 32-bit and 64-bit compilation.

7.2.1 Derived Types

Use the system derived types to make code safe for both the 32-bit and the 64-bit compilation environment. In general, it is good programming practice to use derived types to allow for change. When you use derived data-types, only the system derived types need to change due to data model changes, or due to a port.

The system include files <sys/types.h> and <inttypes.h> contain constants, macros, and derived types that are helpful in making applications 32-bit and 64-bit safe.

7.2.1.1 <sys/types.h>

Include <sys/types.h> in an application source file to gain access to the definition of _LP64 and _ILP32. This header also contains a number of basic derived types that should be used whenever appropriate. In particular, the following are of special interest:

All of these types remain 32-bit quantities in the ILP32 compilation environment and grow to 64-bit quantities in the LP64 compilation environment.

7.2.1.2 <inttypes.h>

The include file <inttypes.h> provides constants, macros, and derived types that help you make your code compatible with explicitly sized data items, independent of the compilation environment. It contains mechanisms for manipulating 8-bit, 16-bit, 32-bit, and 64-bit objects. The file is part of the new 1999 ISO/IEC C standard and the contents of the file track the proposals leading to its inclusion in the 1999 ISO/IEC C standard. The file will soon be updated to fully conform with the 1999 ISO/IEC C standard. The following is a discussion of the basic features provided by <inttypes.h>:

The following sections provide more information about the basic features of <inttypes.h>.

Fixed-Width Integer Types

The fixed-width integer types that <inttypes.h> provides, include signed integer types, such as int8_t, int16_t, int32_t, int64_t, and unsigned integer types, such as uint8_t, uint16_t, uint32_t, and uint64_t.

Derived types defined as the smallest integer types that can hold the specified number of bits include int_least8_t,…, int_least64_t, uint_least8_t,…, uint_least64_t.

It is safe to use an int or unsigned int for such operations as loop counters and file descriptors; it is also safe to use a long for an array index. However, do not use these fixed-width types indiscriminately. Use fixed-width types for explicit binary representations of the following:

Helpful Types Such as unintptr_t

The <inttypes.h> file includes signed and unsigned integer types large enough to hold a pointer. These are given as intptr_t and uintptr_t. In addition, <inttypes.h> provides intmax_t and uintmax_t, which are the longest (in bits) signed and unsigned integer types available.

Use the uintptr_t type as the integral type for pointers instead of a fundamental type such as unsigned long. Even though an unsigned long is the same size as a pointer in both the ILP32 and LP64 data models, using uintptr_t means that only the definition of uintptr_t is effected if the data model changes. This makes your code portable to many other systems. It is also a more clear way to express your intentions in C.

The intptr_t and uintptr_t types are extremely useful for casting pointers when you want to perform address arithmetic. Use intptr_t and uintptr_t types instead of long or unsigned long for this purpose.

Constant Macros

Use the macros INT8_C(c), …, INT64_C(c), UINT8_C(c),…, UINT64_C(c) to specify the size and sign of a given constant. Basically, these macros place an l, ul, ll, or ull at the end of the constant, if necessary. For example, INT64_C(1) appends ll to the constant 1 for ILP32 and an l for LP64.

Use the INTMAX_C(c) and UINTMAX_C(c) macros to make a constant the biggest type. These macros can be very useful for specifying the type of constants described in 7.3 Converting to the LP64 Data Type Model.

Limits

The limits defined by <inttypes.h> are constants that specify the minimum and maximum values of various integer types. This includes minimum and maximum values for each of the fixed-width types such as INT8_MIN,…, INT64_MIN, INT8_MAX,…, INT64_MAX, and their unsigned counterparts.

The <inttypes.h> file also provides the minimum and maximum for each of the least-sized types. These include INT_LEAST8_MIN,…, INT_LEAST64_MIN, INT_LEAST8_MAX,…, INT_LEAST64_MAX, as well as their unsigned counterparts.

Finally, <inttypes.h> defines the minimum and maximum value of the largest supported integer types. These include INTMAX_MIN and INTMAX_MAX and their corresponding unsigned versions.

Format String Macros

The <inttypes.h> file also includes the macros that specify the printf(3S) and scanf(3S) format specifiers. Essentially, these macros prepend the format specifier with an l or ll to identify the argument as a long or long long, given that the number of bits in the argument is built into the name of the macro.

There are macros for printf(3S) that print both the smallest and largest integer types in decimal, octal, unsigned, and hexadecimal formats as the following example shows:


int64_t i;
printf("i =%" PRIx64 "\n", i);

Similarly, there are macros for scanf(3S)that read both the smallest and largest integer types in decimal, octal, unsigned, and hexadecimal formats.


uint64_t u;
scanf("%" SCNu64 "\n", &u);

Do not use these macros indiscriminately. They are best used in conjunction with the fixed-width types discussed in Fixed-Width Integer Types.

7.2.2 Tools

The lint program’s -errchk option detects potential 64-bit porting problems. You can also specify cc -v which directs the compiler to perform additional and more strict semantic checks than by compiling without -v. The -v option also enables certain lint-like checks on the named files.

When you enhance code to be 64-bit safe, use the header files present in the Solaris operating system because these files have the correct definition of the derived types and data structures for the 64-bit compilation environment.

7.2.2.1 lint

Use lint to check code that is written for both the 32-bit and the 64-bit compilation environment. Specify the -errchk=longptr64 option to generate LP64 warnings. Also use the -errchk=longptr64 flag which checks portability to an environment for which the size of long integers and pointers is 64 bits and the size of plain integers is 32 bits. The -errchk=longptr64 flag checks assignments of pointer expressions and long integer expressions to plain integers, even when explicit casts are used.

Use the -errchk=longptr64,signext option to find code where the normal ISO C value-preserving rules allow the extension of the sign of a signed-integral value in an expression of unsigned-integral type.

Use the -Xarch=v9 option of lint when you want to check code that you intend to run in the Solaris 64-bit compilation environment only. Use -Xarch=amd64 when you want to check code you intend to run in the x86 64-bit environment.

When lint generates warnings, it prints the line number of the offending code, a message that describes the problem, and whether or not a pointer is involved. The warning message also indicates the sizes of the involved data types. When you know a pointer is involved and you know the size of the data types, you can find specific 64-bit problems and avoid the pre-existing problems between 32-bit and smaller types.

Be aware, however, that even though lint gives warnings about potential 64-bit problems, it cannot detect all problems. Also, in many cases, code that is intentional and correct for the application generates a warning.

You can suppress the warning for a given line of code by placing a comment of the form “NOTE(LINTED(“<optional message”>))” on the previous line. This is useful when you want lint to ignore certain lines of code such as casts and assignments. Exercise extreme care when you use the “NOTE(LINTED(“<optional message”>))” comment because it can mask real problems. When you use NOTE, include #include<note.h>. Refer to the lint man page for more information.