Jump to content United States-English
HP.com Home Products and Services Support and Drivers Solutions How to Buy
» Contact HP
More options
HP.com home
HP-UX 64-bit Porting and Transition Guide: HP 9000 Computers > Chapter 4 Transitioning C and aC++ Programs to 64-bit Mode

Step 3: Make Source Code Changes

» 

Technical documentation

Complete book in PDF
» Feedback
Content starts here

 » Table of Contents

 » Glossary

 » Index

If you are already using lint along with the HP C +w1 compile-line option, your port to the LP64 data model should be straightforward. When transitioning to 64-bit mode, strive to maintain a single set of source files and header files for both data models. Consider the following guidelines before porting to 64-bit mode:

Avoid Assigning longs to ints

Data can be truncated when longs are assigned to ints.

To avoid this data truncation problem, change long to int assignments to assignments with the same data types.

Example 4-1 Simple Assignment Truncation

Before:

int a;
long b;
. . .
a = b; /* if b > maximum value of a 32-bit integer, then
the result of the assignment is truncated. */

Solution:

Decide if variable b must be long. If it must be long, make both variables long. Otherwise, make both variables int.

Example 4-2 Bit Shift Truncation

The following long to int assignment causes an overflow condition, which leads to unexpected results:

Before:

#include <limits.h>
int main()
{
long base = LONG_MAX;
int final_result;
final_result = base << (LONG_BIT-1); /* LONG_BIT-1 = 63 bits */
printf("%016x\n",final_result);
}

The hex value for the base variable before the bit shift is:

0x 7FFF FFFF FFFF FFFF

The hex value of the intermediate result of the bit shift is:

0x 8000 0000 0000 0000

This 64-bit intermediate result is truncated when assigned to final_result. The final_result is:

0x 0000 0000 0000 0000

This code works in 32-bit mode since int and long are the same size. The code produces unexpected results in 64-bit mode since final_result is no longer big enough to hold the long value in base.

Solution:

To fix this code, make the variables final_result and base the same data type.

Diagnostic Message:

HP C generates the following LP64 migration warning for the above two examples when +M2 and +DD64 are enabled:

warning 720: LP64 migration: Assignment may overflow integer
variable_name.

Avoid Arithmetic between Signed and Unsigned Numbers

Data is promoted differently in 64-bit mode than in 32-bit mode when unsigned ints are compared to longs, and when ints are compared to unsigned longs.

To avoid unintended data promotion problems, ANSI C programs should perform arithmetic operations and comparisons only when all operands are signed or when all operands are unsigned.

Example 4-3 Comparison between Signed and Unsigned Numbers

The following program yields different results in 32-bit mode and 64-bit mode:

Before:

 1  int main()
2 {
3 long L = -1;
4 unsigned int i= 1;
5 if (L > i)
6 printf ("L is greater than i\n");
7 else
8 printf ("L is not greater than i\n");
9 return 0;
10 }

In 32-bit ANSI C mode, the long value of -1 is promoted to an unsigned 32-bit number, making it a large positive number. In 32-bit mode, this program prints:

L is greater than i

In 64-bit ANSI C mode, both operands are promoted to signed 64-bit numbers. In 64-bit mode, this program prints:

L is not greater than i

Diagnostic Message:

HP C generates the following LP64 migration warning for this example when +M2 and +DD64 are enabled:

line 5: warning 734: LP64 migration: Different types treated as
signed for >.

Solution:

The code should be fixed so it produces consistent results in 32-bit and 64-bit mode. Either declare i as long:

long i = 1;

or cast i to a long:

if (L > (long) i);

Avoid Storing Pointers in ints

Pointers will be truncated in 64-bit mode if they are assigned to ints.

To avoid truncation of pointers, store memory addresses in variables declared as pointers or declared with intptr_t (defined in <inttypes.h>). Store the differences between two pointers in variables declared with ptrdiff_t (defined in <stddef.h>).

Before:

int i;           / * 32-bit data type  * /
int j = &i; / * This causes unexpected results. * /

Diagnostic Message:

HP C generates the following LP64 migration warning for this example when +M2 and +DD64 are enabled:

warning 727: LP64 migration: Initialization truncates pointer
into 32-bit integer.

Solution:

The solution uses the intptr_t type definition. This construct is portable across 32-bit and 64-bit platforms.

#include <inttypes.h>
int i; / * 32-bit data type * /
intptr_t j = &i; / * This uses the portable typedef. * /

Avoid Truncating Function Return Values

The return value from function calls can be truncated if its data type is larger than or incompatible with the variable to which it is assigned.

Be aware that the C compiler assumes that functions return a value of type int, unless the function is properly declared.

To avoid truncation of function return values, use ANSI C function prototypes for user-defined functions and standard header files for C library functions.

Example 4-4 Function Prototype Truncation

In the following example, calculate_offset() returns the difference between two pointers. In 32-bit mode, the result can be assigned to an int or long. In 64-bit mode, the result must be assigned to a 64-bit long or ptrdiff_t, as defined in <stddef.h>. The size of a pointer type may vary from platform to platform and from mode to mode. Therefore, using ptrdiff_t protects your application from different pointer sizes.

Before:

 1  int calculate_offset(int *base_address, int *ptr);
2 int main()
3 {
4 int *base_address, *ptr;
5 int offset;
6 offset = calculate_offset(base_address, ptr);
7 printf("The value of the pointer offset is \
8 %d\n", offset);
9 return 0;
10 }
11 int calculate_offset(int *base_address, int *ptr)
12 {
13 return (ptr - base_address);
14 }

Diagnostic Message:

line 12: warning 720: LP64 migration: Return may overflow integer.

Solution:

One solution is to replace the int return type defined in the function prototype and in calculate_offset() with the portable ptrdiff_t:

#include <stddef.h>
ptrdiff_t calculate_offset(int *base_address, int *ptr);

In 64-bit mode, this type definition is a long.

Example 4-5 System Library Truncation of Return Values

The C library function malloc() returns a pointer. In the following example, a function prototype is not defined for malloc(). This leads to a run-time abort because the pointer returned by malloc() is truncated to an int.

Before:

1  int main ()
2 {
3 int *buffer;
4 buffer = malloc (sizeof(int));
5 *buffer = 1234;
6 printf ("The address of buffer is %p\n", &buffer );
7 printf ("The contents of buffer are %p\n", buffer );
8 printf ("The dereferenced value of buffer is \
9 %d\n", *buffer );
10 return 0;
11 }

At run time, this program aborts with a segmentation fault.

Diagnostic Message:

HP C generates the following LP64 migration warning for the above example when +M2 and +DD64 are enabled:

line 4: warning 724: LP64 migration: Assignment converts
default int return type to pointer "buffer".

Solution:

One way to fix this code is to include the <stdlib.h> header file, which contains the ANSI C function prototype and the K&R C function declaration for malloc():

 1  #include <stdlib.h>
2 int main ()
3 {
4 int *buffer;
5 buffer = malloc (sizeof(int));
6 *buffer = 1234;
7 printf ("The address of buffer is %p\n", &buffer );
8 printf ("The contents of buffer are %p\n", buffer );
9 printf ("The dereferenced value of buffer is %d\n", *buffer );
10 return 0;
11 }

At run time, this program now displays:

The address of buffer is 800003ffff8004d8
The contents of buffer are 8000000000005e60
The dereferenced value of buffer is 1234

Avoid Passing Invalid Structure References

HP C no longer treats a structure passed by value the same as a structure passed by reference. When calling functions that expect a pointer to a structure, be sure to explicitly pass a pointer to the structure.

Before:

1   struct   st_tag {int i;} a;
2 int main() {
3 int I;
4 I = foo(a);
5 return I;
6 }
7 int foo (struct st_tag *x) {
8 return (x->i);
9 }

Diagnostic Message:

By default, lint provides the following warning for this example:

FTN arg conflict, struct/union passed instead of ptr to
struct/union
foo( arg 1 ) ex.c(7) :: ex.c(4)

Solution:

1   struct   st_tag {int i;} *a;
2 int main() {
3 int I;
4 I = foo(&a); /* Pass address of struct. */
5 return I;
6 }
. . .

The solution is to pass the address of struct a rather than the structure itself to foo().

Avoid Pointer Arithmetic between longs and ints

Dereferencing pointers using the wrong data type can yield incorrect results. In ILP32, a long pointer can be used to dereference an int value, and an int pointer can be used to dereference a long value because both values are the same length and alignment. In 64-bit mode, if a long value is dereferenced using an int pointer, only the first 32 bits of the 64-bit value will be retrieved.

Before:

 1  int main()
2 {
3 long array[5];
4 int i; /* i: index for array */
5 int *j; /* j: pointer to int */
6 long *k; /* k: pointer to long */
7 for (i = 0; i < 4; i++)
8 array[i]=i + 1;
9
10 j = array + 2; /* incrementing pointer */
11 printf ( "The address of j is %p\n", &j );
12 printf ( "The contents of j are %p\n", j );
13 printf ("The dereferenced value of j is %d\n", *j );
14
15 k = array + 2; /* incrementing pointer */
16 printf ( "The address of k is %p\n", &k );
17 printf ( "The contents of k are %p\n", k );
18 printf ( "The dereferenced value of k is %d\n", *k );
19 return 0;
20 }

At run time, this program prints the following result:

The address of j is 800003ffff800510
The contents of j are 800003ffff8004f0
The dereferenced value of j is 0

The address of k is 800003ffff800518
The contents of k are 800003ffff8004f0
The dereferenced value of k is 3

Both j and k point to the same address, 800003ffff8004f0. Since j is a pointer to an int, it only displays the first 4 bytes of the array, while k displays the entire 8 bytes as shown:

Figure 4-1 Title not available (Avoid Pointer Arithmetic between longs and ints )

Diagnostic Message:

HP C generates the following LP64 migration warning for this example when +M2 and +DD64 are enabled:

line 10: warning 728: LP64 migration: Assignment converts long*
to int* "j".

Solution:

The solution is to change j from an int pointer to a long pointer because the object of the pointer is an array of longs.

 1  int main()
2 {
3 long array[5];
4 int i; /* i: index for array */
5 long *j; /* j: pointer to long */
6 long *k; /* k: pointer to long */
. . .

Avoid Casting Pointers to ints or ints to Pointers

Casts made between pointers and ints will lead to unexpected results in 64-bit mode because pointers and ints are no longer the same size.

The following program aborts because of pointer truncation that results from casting a pointer as an int and storing the results in a 32-bit variable:

1  int main()
2 {
3 int i = 7;
4 int j;
5 int *p;
6 p = &i;
7 j = (int)p;
8 p = j;
9 j=*p;
10 return 0;
11 }

Diagnostic Message:

HP C generates the following LP64 migration warning for this example when +M2 and +DD64 are enabled:

line 7: warning 727: LP64 migration: Cast truncates pointer
into 32 bit integer.

line 8: warning 725: LP64 migration: Assignment converts 32 bit
integer to pointer "p".

Avoid Using Unnamed and Unqualified Bit Fields

The default run time behavior changes for programs that use unqualified or unnamed bit fields in 64-bit mode.

To avoid data alignment problems and unexpected results in programs that use bit fields, follow these guidelines:

  • Use named bit fields if you expect bit fields to affect structure alignment.

  • Always explicitly declare bit fields as signed or unsigned variables in ANSI C.

Before:

 1  struct {
2 char foo;
3 long : 3; /* Unnamed bit field */
4 short c:5;
5 } mystruct;
6 int main()
7 {
8 mystruct.c = -1;
9 if (mystruct.c < 0)
10 printf ("The bit field is less than zero \n");
11 /* Go here in ILP32 */
12 else
13 printf ("The bit field is not less than zero \n");
14 /* Go here in LP64 */
15 printf("Size of the struct: %d\n", sizeof (mystruct));
16 return 0;
17 }

In 32-bit mode, this program prints:

The bit field is less than zero.
Size of the struct: 4

In 64-bit mode, this program prints:

The bit field is not less than zero.
Size of the struct: 2

Diagnostic Message:

line 4: warning 751: LP64 migration: Unqualified bitfields
are unsigned by default.

line 1: warning 750: LP64 migration: Unnamed, non-zero
bitfields do not affect alignment.

Solution:

The code should be fixed so it produces consistent results in 32-bit and 64-bit mode. In ANSI C, a portable solution is:

  • name the bit field,

  • declare signed types, and

  • change the long variable to an int.

struct {
char foo;
signed int b: 3;
signed short c: 5;
. . .

Avoid Using Literals and Masks that Assume 32 bits

Programs that assign hardcoded numeric constants to longs in 32-bit mode may get different results in 64-bit mode if they assume a long is 32 bits. In 64-bit mode, this assumption is incorrect.

To avoid setting incorrect bit mask and hexadecimal values, use pre-processor directives with the the __LP64__ predefined type to generate platform specific code. Or, consider using the complement (~) operator.

Example 4-6 Using the __LP64__ Predefined Macro and Complement (~) Operator

In the following example, the programmer wants to set all but the last 4 bits of the long value to 1. This code works in 32-bit mode. However, in 64-bit mode, it sets the leftmost 32 bits to 0, the next 28 bits to 1, and the last 4 bits to 0.

Before:

long L = 0xFFFFFFF0;   / * 32-bit hex constant '1111...0000' * /

Solution 1:

#ifdef __LP64__
long L = 0xFFFFFFFFFFFFFFF0;
#else
long L = 0xFFFFFFF0;
#endif

The solution ifdefs the long to the correct 64-bit value.

Solution 2:

long L = ~0xFL;   /* Sets the rightmost 4 bits to 0.  */

The solution uses the complement (~) operator to reverse each bit in the operand. The suffix L is required for the long value so that HP C uses the correct length for the constant.

Avoid Hardcoding Size of Data Types

grep for hardcoded system constants that represent sizes of data types.

Here are some common 32-bit system constants:

4

number of bytes in a 32-bit pointer

32

number of bits in a 32-bit pointer

2147483647

maximum value of 32-bit signed integer

-2147483648

minimum value of 32-bit signed integer

4294967295

maximum value of 32-bit unsigned integer

0x7fffffff

maximum value of 32-bit signed integer in hexadecimal format

0xffffffff

maximum value of 32-bit unsigned integer in hexadecimal format

0x80000000

minimum value of 32-bit signed integer in hexadecimal format

Instead of hardcoding values, use the macro values in include files, such as <limits.h> and <inttypes.h>. Examples of macros in <limits.h> include INT_MAX, LONG_MAX, and CHAR_BIT. See the man page for limits(5) for more information. A better solution is to use the sizeof() operator.

Before:

offset = n * 4;            /*  Assumes long is 32 bits.        */

Solution 1:

offset = n * sizeof(long); /*  Works if long is 32 bits
or 64 bits. */

Solution 2:

long val;
offset = n * sizeof(val); /* Optimal! The algorithm adjusts
to current size of val. */

Avoid Hardcoding Bit Shift Values

Check for hardcoded values in bit shift operations. The following examples only get expected results in 32-bit mode because they all hardcode a shift range of 32 bits:

Before:

unsigned long n, newexp;
n = n >> (32 - newexp); /* Not portable! Assumes long is 32 bits.*/

Solution:

#include <limits.h>
unsigned long n, newexp;
n = n >> (LONG_BIT - newexp);

Uses the portable LONG_BIT from <limits.h>.

Before:

Here is an example that turns on the high bit (signed or leftmost bit):

long n;
n = 1 << 32-1; /* Not portable! Assumes long is 32 bits. */

Solution 1:

#include <limits.h>
long n;
n = ~ LONG_MAX; /* This is portable. */

Solution 2:

#include <limits.h>
long n;
n = 1L << LONG_BIT-1; /* This is portable. */

Avoid Hardcoding Constants with malloc(), memory(3), string(3)

Check for hardcoded constants in programs that use the memory(3C) and string(3C) family of functions. Unfortunately, hardcoding constants is common when the following routines are used:

  • malloc(3C) family of memory allocators — realloc(), calloc(), valloc(), alloc()

  • memory(3C) family of memory operators — memccpy(), memcmp(), memcpy(), memmove()

  • string(3C) family of string operators — strcpy(), strcat(), strncmp(), strncpy()

Before:

#include <stdlib.h>
#define BSIZE 4096 /* Buffer size */
char **pointer_buffer;
pointer_buffer=(char **)malloc(BSIZE*4); /* Assumes pointer
is 4 bytes. */

The pointer_buffer is a buffer of pointers. The malloc() function attempts to allocate enough space for this buffer. This code does not allocate enough space for pointer_buffer because the size of a pointer is 8 bytes in 64-bit mode. Silent data corruption or a core dump can happen when the upper bound of the buffer is reached.

Diagnostic Message:

None.

Solution:

#include BSIZE 4096     /* Buffer size */
#include <stdlib.h>
void **pointer_buffer;
pointer_buffer=(void **)malloc(BSIZE*(sizeof(void *)));

The sizeof(void *) operator replaces the hardcoding of the size of a pointer.

Use Appropriate Print Specifiers

When using varargs, stdarg, or variable argument interfaces such as printf(), you must ensure that the algorithms that use the variable parameters are consistent with the 64-bit data model.

Before:

long value;
printf("value = %d\n", value);

The d specifier prints a 32-bit integer.

After:

long value;
printf("value = %ld\n", value);

The ld specifier prints a 64-bit integer in 64-mode and a 32-bit integer in 32-bit mode.

Before:

long offset;
printf("offset =%x\n", offset);

The x specifier prints a 32-bit hexadecimal number.

After:

#include <inttypes.h>
int64_t offset;
printf("offset =" %PRIx64 "\n", offset);

The PRIx64 macro prints a 64-bit integer in both modes.

Printable version
Privacy statement Using this site means you accept its terms Feedback to webmaster
© 1998 Hewlett-Packard Development Company, L.P.