32-bit to 64-bit Migration Considerations

This section outlines various portability considerations in moving C and C++ programs from 32-bit to 64-bit mode.

 

Constants
The limits of constants change. This table shows changed items in the limits.h header file, their hexadecimal value, and decimal equivalent. The equation gives an idea of how to construct these values.

Type Hexadecimal Equation Decimal
signed long min (LONG_MIN)
0x8000000000000000L
-(263)
-9,223,372,036,854,775,808
signed long max (LONG_MAX)
0x7FFFFFFFFFFFFFFFL
263-1 (-LONG_MIN-1)
+9,223,372,036,854,775,807
unsigned long max (ULONG_MAX)
0xFFFFFFFFFFFFFFFFUL
264-1
+18,446,744,073,709,551,616

In C and C++, type identification of constants follows explicit rules. However, programs that use constants exceeding the limit (relying on a 2's complement representation) will experience unexpected results in the 64-bit mode. This is especially true of hexadecimal constants and unsuffixed constants, which are more likely to be extended into the 64-bit long type.

Problematic behaviors will generally occur at boundary areas such as:

Some examples of undesirable boundary side effects are:

Constant assigned to long 32 bit mode 64 bit mode
-2,147,483,649 (INT_MIN-1)
+2,147,483,647
-2,147,483,649
+2,147,483,648 (INT_MAX+1)
-2,147,483,648
+2,147,483,648
+4,294,496,726 (UINT_MAX+1)
0
+4,294,967,296
0xFFFFFFFF (UINT_MAX) 
-1
+4,294,496,295
0x100000000 (UINT_MAX+1)
0
+4,294,967,296
0xFFFFFFFFFFFFFFFF (ULONG_MAX)
-1
-1

Currently, the compiler gives out of range warning messages when attempting to assign a value larger than the designated range into a long type. The warning message is:

1506-207 (W) Integer constant 0x100000000 out of range. 

This warning message may not appear for every case.

When you bit left-shift a 32-bit constant and assign it into a long type, signed values are sign-extended and unsigned values are zero-extended. The examples in the table below show the effects of performing a bit-shift on both 32- and 64-bit constants, using the following code segment:

long l=constantL<<1;
Initial Constant Value Constant Value after Bit-Shift
32-bit 64-bit
0x7FFFFFFFL (INT_MAX)
0xFFFFFFFE
0xFFFFFFFE
0x80000000L (INT_MIN)
0
0x100000000
0xFFFFFFFFL (UINT_MAX)
0xFFFFFFFE
0x1FFFFFFFE

 

Unsuffixed constants can lead to type ambiguity that can impact other parts of your program, such as the result of sizeof operations. For example, in 32-bit mode the compiler types a number like 4294967295 (UINT_MAX) as an unsigned long. In 64-bit mode, this same number becomes a signed long. To avoid this possibility, explicitly add a suffix to all constants that have the potential of impacting constant assignment or expression evaluation in other parts of your program. The fix for the above case is to write the number as 4294967295U. This forces the compiler to always see that constant as an unsigned int regardless of compiler mode.

 

Assignment of Long Variables to Integers and Pointers
Using int and long types in expressions and assignments can lead to implicit conversion through promotions and demotions, or explicit conversions through assignments and argument passing. The following should be avoided:

Assigning a long constant to an integer will cause truncation without warning. For example:

int i; 
long l=2147483648; /* INT_MAX+1*/ 
i=l; 

What will be the value of i? INT_MAX+1 is 2147483647+1 (0x80000000), which becomes INT_MIN when assigned into a signed type. Truncation occurs because the highest bit is treated as a sign bit. The rule here is that there will be a loss of significant digits.

Similar problems occur when passing constants directly to functions, and in functions that return long types. Making explicit use of the L and UL suffix will avoid most, but not all, problems. Alternately, you can avoid accidental conversions by using explicit prototyping. Another good practice is to avoid implicit type conversion by using explicit type casting to change types.

 

Undeclared Functions
Any function that returns a pointer should be explicitly declared when compiling in 64-bit mode. Otherwise, the compiler will assume the function returns an int and truncate the resulting pointer, even if you were to assign it into a valid pointer.

Code such as:

a=(char *) calloc(25);

which used to work fine in 32-bit mode will in 64-bit mode will now silently get a truncated pointer. Even the type casting will not avoid this because the calloc has already been truncated after the return.

The fix in this case would be to include the appropriate header file, which is stdlib.h.

 

Structure Sizes and Alignments
Structures may face potential porting problems.

The 64-bit specification changes the size, member and structure alignment of all structures that are recompiled in 64-bit mode. Structures with long types and pointers will generally change size and alignment in 64-bit mode. Some structures may not change in size because they happen to fall on an exact 8-byte boundary even in 32-bit mode.

Sharing data structures between 32- and 64-bit processes is no longer possible unless the structure is devoid of pointer and long types. Unions that attempt to share long and int types, or overlay pointers onto int types will now be aligned differently, or be corrupted. In general, all but the simplest structures must be checked for alignment and size dependencies.

The alignment for -qalign=full, power or natural changes for 64-bit mode. Structure members are aligned on their natural boundaries. Long types and pointer types are word-aligned in 32-bit mode, and doubleword aligned in 64-bit mode. Additional spaces could be used for padding members.

The alignment for -qalign=twobyte and -qalign=mac68k are not supported in 64-bit mode.

Structures are aligned according to the strictest aligned member. This remains unchanged from 32-bit mode. Because of the padding introduced by the member alignment, structure alignment may not be exactly the same as in the 32-bit mode. This is especially important when you have arrays of structures which contain pointer or long types. The member alignment will change, most likely leading to the structure alignment to change to doubleword alignment (if there are no long long types, double types and long double types).

 

Bitfields
Structure bitfields are limited to 32 bits, and can be of type signed int, unsigned int or plain int. Bit fields are packed into the current word. Adjacent bit fields that cross a word boundary will start at storage unit. This storage unit is a word in power and full alignment, halfword in the mac68k and twobyte alignment, and byte in the packed alignment. 64-bit bitfields are not supported.

In 32-bit mode, non-integer bitfields are tolerated (but not respected) only in the C extended language level. C++ tolerates non-integer bitfields in any language level.

If you use long bit fields in 64-bit mode, their exact alignment may change in future versions of the compiler, even if the bitfield is under 32 bits in length.

 

Miscellaneous Issues

 

Interlanguage Calls with Fortran
A significant number of applications use C, C++, and Fortran together, by calling each other or sharing files. Such applications are among the early candidates for porting to 64-bit platforms for its abilities to solve larger mathematical models. Experience shows that it is easier to modify data sizes/types on the C side than the Fortran side of such applications. The following table lists the equivalent Fortran type in the different modes.

C/C++ type 32-bit 64-bit
int
INTEGER
INTEGER
unsigned int
LOGICAL
LOGICAL
signed long
INTEGER
INTEGER*8
unsigned long
LOGICAL
LOGICAL*8
pointer
INTEGER
INTEGER*8

A user must not mix XCOFF object formats from different modes. A 32-bit Fortran XCOFF cannot mix with a 64-bit C or C++ XCOFF object and vice versa. Since Fortran77 usually does not have an explicit pointer type, it is common practice to use INTEGER variables to hold C or C++ pointers in 32-bit mode. In 64-bit mode, the user should use INTEGER*8 in Fortran. Fortran90 does have a pointer, but it is unsuitable for conversion to the basic C and C++ types.

In 64-bit mode, Fortran will have a POINTER*8 that is 8 bytes in length as compared to their POINTER which is 4-bytes in length.



Operating System Migration Considerations