Chapter 4 Converting Applications
Two basic issues that regard conversion arise for applications developers:
Trying to maintain a single source with as few #ifdefs as
possible is usually better than trying to maintain multiple source trees. This chapter
provides guidelines for writing code that works correctly in both 32-bit environments
and 64-bit environments. At best, the conversion of current code might require only
a recompilation and relinking with the 64-bit libraries. However, for those cases
where code changes are required, this chapter discusses the tools that help make conversion
easier.
Data Model
As stated previously, the biggest difference between the 32-bit environment
and 64-bit environment is the change in two fundamental data types.
The C data-type model used for 32-bit applications is the ILP32 model, so named
because ints, longs, and pointers are 32-bit. The LP64 data
model is the C data-type model for 64-bit applications. This model was agreed upon
by a consortium of companies across the industry. LP64 is so named because longs and
pointers grow to 64-bit quantities. The remaining C types int, short, and char are the same as in the ILP32 model.
The following sample program, foo.c, directly illustrates
the effect of the LP64 data model in contrast to the ILP32 data models. The same program
can be compiled as either a 32–bit program or a 64–bit program.
#include <stdio.h>
int
main(int argc, char *argv[])
{
(void) printf("char is \t\t%lu bytes\n", sizeof (char));
(void) printf("short is \t%lu bytes\n", sizeof (short));
(void) printf("int is \t\t%lu bytes\n", sizeof (int));
(void) printf("long is \t\t%lu bytes\n", sizeof (long));
(void) printf("long long is \t\t%lu bytes\n", sizeof (long long));
(void) printf("pointer is \t%lu bytes\n", sizeof (void *));
return (0);
}
The result of 32–bit compilation is:
% cc -O -o foo32 foo.c
% foo32
char is 1 bytes
short is 2 bytes
int is 4 bytes
long is 4 bytes
long long is 8 bytes
pointer is 4 bytes
|
The result of 64–bit compilation is:
% cc -xarch=generic64 -O -o foo64 foo.c
% foo64
char is 1 bytes
short is 2 bytes
int is 4 bytes
long is 8 bytes
long long is 8 bytes
pointer is 8 bytes
|
Note –
The default compilation environment is designed to maximize portability,
that is, to create 32–bit applications.
The standard relationship between C integral types still holds true.
sizeof (char) <= sizeof (short) <= sizeof (int) <= sizeof (long)
Table 4–1 lists the basic C types,
and their corresponding sizes in bits in the data type models for both LP32 and LP64.
Table 4–1 Data Type Sizes in Bits
|
C data type
|
ILP32
|
LP64
|
|
char
|
8
|
unchanged
|
|
short
|
16
|
unchanged
|
|
int
|
32
|
unchanged
|
|
long
|
32
|
64
|
|
long long
|
64
|
unchanged
|
|
pointer
|
32
|
64
|
|
enum
|
32
|
unchanged
|
|
float
|
32
|
unchanged
|
|
double
|
64
|
unchanged
|
|
long double
|
128
|
unchanged
|
Some older 32-bit applications use int, long, and
pointer types interchangeably. The size of longs and pointers grow in
the LP64 data model. You need to be aware that this change alone can cause many 32-bit to 64-bit conversion
problems.
In addition, declarations and casts become very important in showing what is
intended. How expressions are evaluated can be affected when the types change. The
effects of standard C conversion rules are influenced by the change in data-type sizes.
To adequately show what is intended, you might need to declare the types of constants.
Casts might also be needed in expressions to make certain that the expression is evaluated
the way that you intended. Correct evaluation of expressions is particularly crucial
in the case of sign extension, where explicit casting might be essential to achieve
the intended effect.
Other problems arise with built-in C operators, format strings, assembly language,
and compatibility and interoperability.
The rest of this chapter advises you how to overcome these problems by:
-
Explaining the problems outlined above in more detail
-
Describing some of the derived types and include files that are useful
to make code safe for both 32-bit and 64-bit
-
Describing the tools available for helping to make code 64-bit safe
-
Providing general rules for making code portable between the 32-bit and 64-bit environments
Implementing Single-Source Code
The sections that follow describe some of the resources available to application
developers that help you write single-source code that supports both 32–bit
and 64–bit compilation.
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. While a detailed discussion of these
is beyond the scope of this document, some are discussed in the sections that follow,
as well as in Appendix A, Changes in Derived Types.
Feature Test Macros
An application source file that includes <sys/types.h> makes
the definitions of the programming model symbols, _LP64 and _ILP32, available through inclusion of <sys/isa_defs.h>.
For information about preprocessor symbols (_LP64 and _ILP32) and macros (_LITTLE_ENDIAN and _BIG_ENDIAN6), see types(3HEAD).
Derived Types
Using the system derived types helps make code 32-bit and 64-bit safe, since
the derived types themselves are safe for both the ILP32 and LP64 data models. In
general, using derived types to allow for change is good programming practice. Should
the data model change in the future, or when porting to a different platform, only
the system derived types need to change rather than the application.
<sys/types.h> File
The <sys/types.h> header contains a number of basic
derived types that should be used whenever appropriate. In particular, the following
are of special interest:
-
clock_t
-
The type clock_t represents the system times in clock
ticks.
-
dev_t
-
The type dev_t is used for device numbers.
-
off_t
-
The type off_t is used for file sizes and offsets.
-
ptrdiff_t
-
The type ptrdiff_t is the signed integral type for the
result of subtracting two pointers.
-
size_t
-
The type size_t is for the size, in bytes, of objects
in memory.
-
ssize_t
-
The signed size type ssize_t is used by functions that
return a count of bytes or an error indication.
-
time_t
-
The type time_t is used for time in seconds.
All of these types remain 32-bit quantities in the ILP32 compilation environment
and grow to 64-bit quantities in the LP64 compilation environment.
The use of some of these types is explained in more detail later in this chapter
under Guidelines for Converting to LP64.
<inttypes.h> File
The include file <inttypes.h> was added to the Solaris
2.6 release to provide constants, macros, and derived types that help programmers
make their 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 an ANSI C proposal and tracks the ISO/JTC1/SC22/WG14 C committee's
working draft for the revision of the current ISO C standard, ISO/IEC 9899:1990 Programming
Language – C.
The basic features provided by <inttypes.h> are:
These are discussed in more detail in the sections that follow.
Fixed-Width Integer Types
The fixed-width integer types provided by <inttypes.h> include
both signed and unsigned integer types, such as int8_t, int16_t, int32_t, int64_t, 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.
These fixed-width types should not be used indiscriminately.
For example, int can continue to be used for such things as loop counters
and file descriptors, and long can be used for array indices. On the
other hand, you should use fixed-width types for explicit binary representations of:
-
On-disk data
-
Over-the-wire data
-
Hardware registers
-
Binary interface specifications (that have explicitly sized objects
or involve sharing or communication between 32–bit and 64–bit programs)
-
Binary data structures (that are used by 32–bit and 64–bit
programs through shared memory, files, and so on)
uintptr_t and Other Helpful Types
Other useful types provided by <inttypes.h> include signed
and unsigned integer types large enough to hold a pointer. These are given as intptr_t and uintptr_t. In addition, intmax_t and uintmax_t are defined to be the longest (in bits) signed and unsigned integer
types available.
Using the uintptr_t type as the integral type for pointers is a
better option than using 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, the use of the uintptr_t requires only the definition
of uintptr_t to change when a different data model is used. This makes
it portable to many other systems. It is also a clearer way to express your intentions
in C.
The intptr_t and uintptr_t types are extremely useful
for casting pointers when you want to do address arithmetic. They should be used instead
of long or unsigned long for this purpose.
Note –
Use of uintptr_t for casting is usually safer than intptr_t, especially for comparisons.
Constant Macros
Macros are provided to specify the size and sign of a given constant. The macros
are INT8_C(c), ..., INT64_C(c), UINT8_C(c),..., UINT64_C(c). 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.
Macros for making a constant the biggest type are INTMAX_C(c) and UINTMAX_C(c). These macros can be very useful for specifying the type of
constants described in Guidelines for Converting to LP64.
Limits Defined by <inttypes.h>
The limits defined by <inttypes.h> are constants specifying
the minimum and maximum values of various integer types. This includes minimum and
maximum values of each of the fixed-width types, such as INT8_MIN,..., INT64_MIN, INT8_MAX,..., INT64_MAX, and their unsigned
counterparts.
The minimum and maximum for each of the least-sized types are given, too. These
include INT_LEAST8_MIN,..., INT_LEAST64_MIN, INT_LEAST8_MAX,..., INT_LEAST64_MAX, and their unsigned counterparts.
Finally, the minimum and maximum value of the largest supported integer types
are defined. These include INTMAX_MIN and INTMAX_MAX and
their corresponding unsigned versions.
Format String Macros
Macros for specifying the printf and scanf format
specifiers are also provided in <inttypes.h> . Essentially,
these macros prepend the format specifier with an l or ll to specify the argument as a long or long long,
given the number of bits in the argument, which is built into the name of the macro.
Macros for printf(3C) format
specifiers exist for printing 8-bit, 16-bit, 32-bit, and 64-bit integers, the smallest
integer types, and the biggest integer types, in decimal, octal, unsigned, and hexadecimal.
For example, printing a 64–bit integer in hexadecimal notation:
int64_t i;
printf("i =%" PRIx64 "\n", i);
Similarly, there are macros for scanf(3C) format
specifiers for reading 8-bit, 16-bit, 32-bit, and 64-bit integers and the biggest
integer type in decimal, octal, unsigned, and hexadecimal. For example, reading an
unsigned 64–bit decimal integer:
uint64_t u;
scanf("%" SCNu64 "\n", &u);
Do not use these macros indiscriminately. They are best used in conjunction
with the fixed-width types. Refer to the section Fixed-Width Integer Types for more details.
Tools Support
The lint program, available with the Sun Studio 10 compiler,
can detect potential 64-bit problems and is useful in making code 64-bit safe. In
addition, the -v option to the C compiler can be very helpful. It
tells the compiler to perform additional and stricter semantic checks. It also enables
certain lint-like checks on the named files.
For more information about the capabilities of the C compilers and lint, see the Sun Studio 10: C User's Guide.
lint for 32–bit and 64–bit
Environments
lint can be used on both 32-bit and 64-bit code. Use the -errchk=longptr64 option for code that is intended to be run in both 32–bit
and 64–bit environments. The -errchk=longptr64 option checks
portability to an environment in which the size of long integers and pointers is 64
bits and the size of plain integers is 32 bits.
The -Xarch=v9 option should be used to lint code intended to be run in the 64–bit SPARC environment. Use
the -errchk=longptr64 option together with the -Xarch=v9 option to generate warnings about potential 64–bit problems for code
to be run on 64–bit SPARC.
Starting with the Solaris 10 release, the -Xarch=amd64 option
should be used to lint code intended to be run in the 64–bit
AMD environment.
Note –
The -D__sparcv9 option to lint is
no longer necessary and should not be used.
For a description of lint options, see the Sun
Studio 10: C User's Guide.
When warnings are generated, lint(1) prints the line number
of the offending code, a warning message that describes the problem, and notes whether
a pointer was involved. It can also indicate the sizes of types involved. The fact
that a pointer is involved and the size of the types can be useful in finding specific 64-bit problems
and avoiding the pre-existing problems between 32-bit and smaller types.
Note –
Though lint gives warnings about potential 64-bit problems,
it cannot detect all problems. You must remember that not all warnings generated by lint are true 64-bit problems. In many cases, code that generates a warning
can be intentional and correct for the application.
The sample program and lint(1) output below illustrate most
of the lint warnings that can arise in code that is not 64–bit
clean.
1 #include <inttypes.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4
5 static char chararray[] = "abcdefghijklmnopqrstuvwxyz";
6
7 static char *myfunc(int i)
8 {
9 return(& chararray[i]);
10 }
11
12 void main(void)
13 {
14 int intx;
15 long longx;
16 char *ptrx;
17
18 (void) scanf("%d", &longx);
19 intx = longx;
20 ptrx = myfunc(longx);
21 (void) printf("%d\n", longx);
22 intx = ptrx;
23 ptrx = intx;
24 intx = (int)longx;
25 ptrx = (char *)intx;
26 intx = 2147483648L;
27 intx = (int) 2147483648L;
28 ptrx = myfunc(2147483648L);
29 }
(19) warning: assignment of 64-bit integer to 32-bit integer
(20) warning: passing 64-bit integer arg, expecting 32-bit integer: myfunc(arg 1)
(22) warning: improper pointer/integer combination: op "="
(22) warning: conversion of pointer loses bits
(23) warning: improper pointer/integer combination: op "="
(23) warning: cast to pointer from 32-bit integer
(24) warning: cast from 64-bit integer to 32-bit integer
(25) warning: cast to pointer from 32-bit integer
(26) warning: 64-bit constant truncated to 32 bits by assignment
(27) warning: cast from 64-bit integer constant expression to 32-bit integer
(28) warning: passing 64-bit integer constant arg, expecting 32-bit integer: myfunc(arg 1)
function argument ( number ) type inconsistent with format
scanf (arg 2) long * :: (format) int * t.c(18)
printf (arg 2) long :: (format) int t.c(21)
(The lint warning that arises from line 27 of this code sample
is issued only if the constant expression will not fit into the type into which it
is being cast.)
Warnings for a given source line can be suppressed by placing a /*LINTED*/ comment on the previous line. This is useful where you have really intended
the code to be a specific way. An example might be in the case of casts and assignments.
Exercise extreme care when using the /*LINTED*/ comment because
it can mask real problems. Refer to the Sun Studio 10: C User's Guide or
the lint(1) man page for more information.
Guidelines for Converting to LP64
When using lint(1), remember that not all problems result
in lint(1) warnings, nor do all lint(1) warnings
indicate that a change is required. Examine each possibility for intent. The examples
that follow illustrate some of the more common problems you are likely to encounter
when converting code. Where appropriate, the corresponding lint(1) warnings
are shown.
Do Not Assume int and Pointers Are
the Same Size
Since ints and pointers are the same size in the ILP32 environment,
a lot of code relies on this assumption. Pointers are often cast to int or unsigned int for address arithmetic. Instead, pointers could be cast to long because long and pointers are the same size in both ILP32
and LP64 worlds. Rather than explicitly using unsigned long, use uintptr_t because it expresses the intent more closely and makes the code more
portable, insulating it against future changes. For example,
char *p;
p = (char *) ((int)p & PAGEOFFSET);
produces the warning:
warning: conversion of pointer loses bits
Using the following code will produce the clean results:
char *p;
p = (char *) ((uintptr_t)p & PAGEOFFSET);
Do Not Assume int and long Are the Same Size
Because ints and longs were never really distinguished
in ILP32, a lot of existing code uses them indiscriminately while implicitly or explicitly
assuming that they are interchangeable. Any code that makes this assumption must be
changed to work for both ILP32 and LP64. While an int and a long are both 32–bits in the ILP32 data model, in the LP64 data model, a long is 64–bits. For example,
int waiting;
long w_io;
long w_swap;
...
waiting = w_io + w_swap;
produces the warning:
warning: assignment of 64-bit integer to 32-bit integer
Sign Extension
Unintended sign extension is a common problem when converting to 64–bits.
It is hard to detect before the problem actually occurs because lint(1) does
not warn you about it. Furthermore, the type conversion and promotion rules are somewhat
obscure. To fix unintended sign extension problems, you must use explicit casting
to achieve the intended results.
To understand why sign extension occurs, it helps to understand the conversion
rules for ANSI C. The conversion rules that seem to cause the most sign extension
problems between 32-bit and 64-bit integral values are:
-
Integral promotion
A char, short, enumerated type, or bit-field, whether signed or unsigned,
can be used in any expression that calls for an int. If an int can hold all possible values of the original type, the value is converted
to an int. Otherwise, it is converted to an unsigned int.
-
Conversion between signed and unsigned integers
When
a negative signed integer is promoted to an unsigned integer of the same or larger
type, it is first promoted to the signed equivalent of the larger type, then converted
to the unsigned value.
For a more detailed discussion of the conversion rules, refer to the ANSI C
standard. Also included in this standard are useful rules for ordinary arithmetic
conversions and integer constants.
When compiled as a 64-bit program, the addr variable in the following
example becomes sign-extended, even though both addr and a.base are unsigned types.
Example 4–1 test.c
struct foo {
unsigned int base:19, rehash:13;
};
main(int argc, char *argv[])
{
struct foo a;
unsigned long addr;
a.base = 0x40000;
addr = a.base << 13; /* Sign extension here! */
printf("addr 0x%lx\n", addr);
addr = (unsigned int)(a.base << 13); /* No sign extension here! */
printf("addr 0x%lx\n", addr);
}
This sign extension occurs because the conversion rules are applied as follows:
-
a.base is converted from an unsigned int to an int because of the integral promotion rule. Thus, the expression a.base << 13 is of type int, but no sign extension has
yet occurred.
-
The expression a.base << 13 is of type int, but it is converted to a long and then to an unsigned
long before being assigned to addr, because of the signed
and unsigned integer promotion rule. The sign extension occurs when it is converted
from an int to a long.
% cc -o test64 -xarch=v9 test.c
% ./test64
addr 0xffffffff80000000
addr 0x80000000
%
|
When this same example is compiled as a 32-bit program it does not display any
sign extension:
% cc -o test32 test.c
% ./test32
addr 0x80000000
addr 0x80000000
%
|
Use Pointer Arithmetic Instead of Address Arithmetic
In general, using pointer arithmetic works better than address arithmetic because
pointer arithmetic is independent of the data model, whereas address arithmetic might
not be. It usually leads to simpler code as well. For example,
int *end;
int *p;
p = malloc(4 * NUM_ELEMENTS);
end = (int *)((unsigned int)p + 4 * NUM_ELEMENTS);
produces the warning:
warning: conversion of pointer loses bits
The following code will produce clean results:
int *end;
int *p;
p = malloc(sizeof (*p) * NUM_ELEMENTS);
end = p + NUM_ELEMENTS;
Repacking a Structure
Extra padding may be added to a structure by the compiler to meet alignment
requirements as long and pointer fields grow to 64 bits for LP64.
For both the SPARCV9 ABI and the amd64 ABI, all types of structures are aligned
to at least the size of the largest quantity within them. A simple rule for repacking
a structure is to move the long and pointer fields to the beginning
of the structure and rearrange the rest of the fields—usually, but not always,
in descending order of size, depending on how well they can be packed. For example,
struct bar {
int i;
long j;
int k;
char *p;
}; /* sizeof (struct bar) = 32 */
For better results, use:
struct bar {
char *p;
long j;
int i;
int k;
}; /* sizeof (struct bar) = 24 */
Note –
The alignment of fundamental types changes between the i386 and amd64 ABIs.
See Alignment Issues.
Check Unions
Be sure to check unions because their fields might have changed sizes between
ILP32 and LP64. For example,
typedef union {
double _d;
long _l[2];
} llx_t;
should be:
typedef union {
double _d;
int _l[2];
} llx_t;
Specify Constant Types
A loss of data can occur in some constant expressions because of lack of precision.
These types of problems are very hard to find. Be explicit about specifying the type(s)
in your constant expressions. Add some combination of {u,U,l,L} to
the end of each integer constant to specify its type. You might also use casts to
specify the type of a constant expression. For example,
int i = 32;
long j = 1 << i; /* j will get 0 because RHS is integer expression */
should be:
int i = 32;
long j = 1L << i;
Beware of Implicit Declaration
For some compilation modes, the compiler might assume the type int for
any function or variable that is used in a module and not defined or declared externally.
Any longs and pointers used in this way are truncated by the compiler's
implicit int declaration. The appropriate extern declaration
for a function or variable should be placed in a header and not in the C module. The
header should then be included by any C module that uses the function or variable.
In the case of a function or variable defined by the system headers, the proper header
should still be included in the code.
For example, because getlogin() is not declared, the following
code:
int
main(int argc, char *argv[])
{
char *name = getlogin()
printf("login = %s\n", name);
return (0);
}
produces the warnings:
warning: improper pointer/integer combination: op "="
warning: cast to pointer from 32-bit integer
implicitly declared to return int
getlogin printf
For better results, use::
#include <unistd.h>
#include <stdio.h>
int
main(int argc, char *argv[])
{
char *name = getlogin();
(void) printf("login = %s\n", name);
return (0);
}
sizeof is an unsigned long
In the LP64 environment, sizeof has the effective type of size_t which is implemented as an unsigned long. Occasionally, sizeof is passed to a function expecting an argument of type int,
or is assigned or cast to an int. In some cases, this truncation might
cause loss of data. For example,
long a[50];
unsigned char size = sizeof (a);
produces the warnings:
warning: 64-bit constant truncated to 8 bits by assignment
warning: initializer does not fit or is out of range: 0x190
Use Casts to Show Your Intentions
Relational expressions can be tricky because of conversion rules. You should
be very explicit about how you want the expression to be evaluated by adding casts
wherever necessary.
Check Format String Conversion Operation
The format strings for printf(3C), sprintf(3C), scanf(3C),
and sscanf(3C) might need
to be changed for long or pointer arguments. For pointer arguments, the
conversion operation given in the format string should be %p to
work in both the 32-bit and 64-bit environments. For example,
char *buf;
struct dev_info *devi;
...
(void) sprintf(buf, "di%x", (void *)devi);
produces the warning:
warning: function argument (number) type inconsistent with format
sprintf (arg 3) void *: (format) int
Use the following code to produce clean results:
char *buf;
struct dev_info *devi;
...
(void) sprintf(buf, "di%p", (void *)devi);
Also check to be sure that the storage pointed to by buf is large
enough to contain 16 digits. For long arguments, the long size
specification, l, should be prepended to the conversion operation
character in the format string. For example,
size_t nbytes;
ulong_t align, addr, raddr, alloc;
printf("kalloca:%d%%%d from heap got %x.%x returns %x\n",
nbytes, align, (int)raddr, (int)(raddr + alloc), (int)addr);
produces the warnings:
warning: cast of 64-bit integer to 32-bit integer
warning: cast of 64-bit integer to 32-bit integer
warning: cast of 64-bit integer to 32-bit integer
The following code will produce clean results:
size_t nbytes;
ulong_t align, addr, raddr, alloc;
printf("kalloca:%lu%%%lu from heap got %lx.%lx returns %lx\n",
nbytes, align, raddr, raddr + alloc, addr);
Other Considerations
The remaining guidelines highlight common problems encountered when converting
an application to a full 64–bit program.
Derived Types That Have Grown in Size
A number of derived types have changed so they represent 64-bit quantities in
the 64-bit application environment. This change does not affect 32-bit applications;
however, any 64-bit applications that consume or export data described by these types
need to be reevaluated for correctness. An example of this is in applications that
directly manipulate the utmpx(4) files.
For correct operation in the 64-bit application environment, you should not attempt to directly access these files. Instead, you should use the getutxent(3C) and related family
of functions.
A list of changed derived types is included in Appendix A, Changes in Derived Types.
Check for Side Effects of Changes
One problem to be aware of is that a type change in one area might result in
an unexpected 64-bit conversion in another area. For example, in the case of a function
that previously returned an int and now returns an ssize_t,
all the callers need to be checked.
Check Whether Literal Uses of long Still
Make Sense
Because a long is 32 bits in the ILP32 model and 64 bits in the
LP64 model, there might be cases where what was previously defined as a long is neither appropriate nor necessary. In this case, it might be possible to
use a more portable derived type.
Related to this, a number of derived types might have changed under the LP64
data model for the reason stated above. For example, pid_t remains a long in the 32-bit environment, but under the 64-bit environment, a pid_t is an int. For a list of derived types modified for the
LP64 compilation environment, see Appendix A, Changes in Derived Types.
Use #ifdef for Explicit 32-bit Versus 64-bit Prototypes
In some cases, specific 32-bit and 64-bit versions of an interface are unavoidable.
In the headers, these would be distinguishable by the use of the _LP64 or _ILP32 feature test macros. Similarly, code that is to work in 32-bit and 64-bit environments
might also need to utilize the appropriate #ifdefs, depending on
the compilation mode.
Algorithmic Changes
After code has been made 64-bit safe, review it again to verify that the algorithms
and data structures still make sense. The data types are larger, so data structures
might use more space. The performance of your code might change as well. Given these
concerns, you might need to adapt your code appropriately.
Checklist for Getting Started
Assuming you need to convert your code to 64-bit, the following checklist might
be helpful:
-
Read this entire document with an emphasis on the Guidelines for Converting to LP64.
-
Review all data structures and interfaces to verify that these are
still valid in the 64-bit environment.
-
Include <sys/types.h> in your code to pull
in the _ILP32 or _LP64 definitions as well as
many basic derived types.
-
Move function prototypes and external declarations with non-local
scope to headers and include these headers in your code.
-
Run lint(1) using the -errchk=longptr64 and
review each warning individually, being aware that not all warnings require a change
to the code. Depending on the resulting changes, you might also want to run lint(1) again, both in 32–bit and 64–bit modes.
-
Compile code as both 32-bit and 64-bit, unless the application is
being provided only as 64-bit.
-
Test the application by executing the 32-bit version on the 32-bit operating
system, and the 64-bit version on the 64-bit operating system. You could include testing
the 32-bit version on the 64-bit operating system, but this is not necessary.
Sample Program
The following sample program, foo.c, directly illustrates
the effect of the LP64 data model in contrast to the ILP32 data models. The same program
can be compiled as either a 32–bit program or a 64–bit program.
#include <stdio.h>
int
main(int argc, char *argv[])
{
(void) printf("char is \t\t%lu bytes\n", sizeof (char));
(void) printf("short is \t%lu bytes\n", sizeof (short));
(void) printf("int is \t\t%lu bytes\n", sizeof (int));
(void) printf("long is \t\t%lu bytes\n", sizeof (long));
(void) printf("long long is \t\t%lu bytes\n", sizeof (long long));
(void) printf("pointer is \t%lu bytes\n", sizeof (void *));
return (0);
}
The result of 32–bit compilation is:
% cc -O -o foo32 foo.c
% foo32
char is 1 bytes
short is 2 bytes
int is 4 bytes
long is 4 bytes
long long is 8 bytes
pointer is 4 bytes
|
The result of 64–bit compilation is:
% cc -xarch=generic64 -O -o foo64 foo.c
% foo64
char is 1 bytes
short is 2 bytes
int is 4 bytes
long is 8 bytes
long long is 8 bytes
pointer is 8 bytes
|
Note –
The default compilation environment is designed to maximize portability,
that is, to create 32–bit applications.