Solaris 7 64-bit Developer's Guide

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 instead because it expresses the intent more closely and makes the code more portable, insulating it against future changes.


Example 4-3

char *p;
p = (char *) ((int)p & PAGEOFFSET);

%
warning: conversion of pointer loses bits

Suggested use:

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.


Example 4-4

int waiting;
long w_io;
long w_swap;
...
waiting = w_io + w_swap;

%
warning: assignment of 64-bit integer to 32-bit integer

Sign Extension

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 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:

  1. 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.

  2. 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-5

%cat 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:

  1. 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.

  2. 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 signed and unsigned integer promotion rules. 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.


Example 4-6

int *end;
int *p;
p = malloc(4 * NUM_ELEMENTS);
end = (int *)((unsigned int)p + 4 * NUM_ELEMENTS);

%
warning: conversion of pointer loses bits

Suggested use:


int *end; 
int *p;
p = malloc(sizeof (*p) * NUM_ELEMENTS);
end = p + NUM_ELEMENTS;

Repack Structures

Internal data structures in applications should be checked for holes. Extra padding between fields in the structure to meet alignment requirements can be used, since any long or pointer fields will grow to 64 bits for LP64. In the 64-bit environment on SPARC platforms, all types of structures are aligned to at least the size of the largest quantity within them. A simple rule for repacking the structure is to move the long and pointer fields to the beginning of the structure.


Example 4-7

struct bar {
		int i;
		long j;
		int k;
		char *p;
};			/* sizeof (struct bar) = 32 */

Suggested use:


struct bar {
		char *p;
		long j;
		int i;
		int k;
};			/* sizeof (struct bar) = 24 */

Check Unions

Be sure to check unions because their fields might have changed sizes between ILP32 and LP64.


Example 4-8

typedef union {
       double   _d;
       long _l[2];
} llx_t;

Suggested use:


typedef	 union {
			double _d;
 		int _l[2];
} llx_t;

Specify Type of Constants

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.


Example 4-9

int i = 32;
long j = 1 << i;		/* j will get 0 because RHS is integer */
                    	/* expression */

Suggested use:


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 the function or variable should be placed in a header and not in the C module. This header should then be included by any C module that uses the function or variable. If this is a function or variable defined by the system headers, the proper header should still be included in the code.


Example 4-10

int
main(int argc, char *argv[])
{
		char *name = getlogin()
		printf("login = %s\n", name);
		return (0);
}
		
%
warning: improper pointer/integer combination: op "="
warning: cast to pointer from 32-bit integer
implicitly declared to return int 
getlogin        printf   

Suggested 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 assigned or cast to an int. In some cases, this truncation might cause loss of data.


Example 4-11

long a[50];
unsigned char size = sizeof (a);

%
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(3S), sprintf(3S), scanf(3S), and sscanf(3S) 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.


Example 4-12

char *buf;
struct dev_info *devi;
...
(void) sprintf(buf, "di%x", (void *)devi);

%
warning: function argument (number) type inconsistent with format
  sprintf (arg 3)     void *: (format) int

Suggested use:


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.



Example 4-13

size_t nbytes;
u_long 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);

%
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

Suggested use:


size_t nbytes;
u_long align, addr, raddr, alloc;
printf("kalloca:%lu%%%lu from heap got%lx.%lx returns%lx\n", 
nbytes, align, raddr, raddr + alloc, addr);