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
The C compiler from Sun WorkShop assumes a 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 LP64, sizeof has the effective type of 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); |
For long arguments, the long size specification, l, should be prepended to the conversion operation character in the format string. Furthermore, check to be sure that the storage pointed to by buf is large enough to contain 16 digits. 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.
Calling Convention Changes
When passing structures by value for SPARC V9, if the structure is small enough, it is passed in registers rather than as a pointer to a copy. This can cause problems when passing structures between C code and hand-written assembly code.
Floating point parameters work in a similar fashion; some floating point arguments passed by value are passed in floating point registers.
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.