JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Oracle Solaris Studio 12.2: C User's Guide
search filter icon
search icon

Document Information

Preface

1.  Introduction to the C Compiler

2.  C-Compiler Implementation-Specific Information

3.  Parallelizing C Code

4.  lint Source Code Checker

5.  Type-Based Alias Analysis

6.  Transitioning to ISO C

7.  Converting Applications for a 64-Bit Environment

7.1 Overview of the Data Model Differences

7.2 Implementing Single Source Code

7.2.1 Derived Types

7.2.1.1 <sys/types.h>

7.2.1.2 <inttypes.h>

Fixed-Width Integer Types

Helpful Types Such as unintptr_t

Constant Macros

Limits

Format String Macros

7.2.2 Tools

7.2.2.1 lint

7.3 Converting to the LP64 Data Type Model

7.3.1 Integer and Pointer Size Change

7.3.2 Integer and Long Size Change

7.3.3 Sign Extension

7.3.4 Pointer Arithmetic Instead of Integers

7.3.5 Structures

7.3.6 Unions

7.3.7 Type Constants

7.3.8 Beware of Implicit Declarations

7.3.9 sizeof( ) Is an Unsigned long

7.3.10 Use Casts to Show Your Intentions

7.3.11 Check Format String Conversion Operation

7.4 Other Considerations

7.4.1 Derived Types That Have Grown in Size

7.4.2 Check for Side Effects of Changes

7.4.3 Check Whether Literal Uses of long Still Make Sense

7.4.4 Use #ifdef for Explicit 32-bit Versus 64-bit Prototypes

7.4.5 Calling Convention Changes

7.4.6 Algorithm Changes

7.5 Checklist for Getting Started

8.  cscope: Interactively Examining a C Program

A.  Compiler Options Grouped by Functionality

B.  C Compiler Options Reference

C.  Implementation-Defined ISO/IEC C99 Behavior

D.  Supported Features of C99

E.  Implementation-Defined ISO/IEC C90 Behavior

F.  ISO C Data Representations

G.  Performance Tuning

H.  The Differences Between K&R Solaris Studio C and Solaris Studio ISO C

Index

7.3 Converting to the LP64 Data Type Model

The examples that follow illustrate some of the more common problems you are likely to encounter when you convert code. Where appropriate, the corresponding lint warnings are shown.

7.3.1 Integer and Pointer Size Change

Since integers and pointers are the same size in the ILP32 compilation environment, some code relies on this assumption. Pointers are often cast to int or unsigned int for address arithmetic. Instead, cast your pointers to long because long and pointers are the same size in both ILP32 and LP64 data-type models. Rather than explicitly using unsigned long, use uintptr_t instead because it expresses your intent more closely and makes the code more portable, insulating it against future changes. Consider the following example:

char *p;
p = (char *) ((int)p & PAGEOFFSET);
%
warning: conversion of pointer loses bits

Here is the modified version:

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

7.3.2 Integer and Long Size Change

Because integers and longs are never really distinguished in the ILP32 data-type model, your existing code probably uses them indiscriminately. Modify any code that uses integers and longs interchangeably so it conforms to the requirements of both the ILP32 and LP64 data-type models. While an integer and a long are both 32-bits in the ILP32 data-type model, a long is 64 bits in the LP64 data-type model.

Consider the following example:

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

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

Furthermore, large arrays of integers, such as longs or unsigned longs, can cause serious performance degradation in the LP64 data-type model as compared to arrays of ints or unsigned ints. Large arrays of longs or unsigned longs can also cause significantly more cache misses and consume more memory.

Therefore, if int works just as well as long for the application purposes, it’s better to use int rather than long.

This is also an argument for using arrays of ints instead of arrays of pointers. Some C applications suffer from serious performance degradation after conversion to the LP64 data-type model because they rely on many, large, arrays of pointers.

7.3.3 Sign Extension

Sign extension is a common problem when you convert to the 64-bit compilation environment because the type conversion and promotion rules are somewhat obscure. To prevent sign extension problems, use explicit casting to achieve the intended results.

To understand why sign extension occurs, it helps to understand the conversion rules for ISO C. The conversion rules that seem to cause the most sign extension problems between the 32-bit and the 64-bit compilation environment come into effect during the following operations:

When the following example is compiled as a 64-bit program, the addr variable becomes sign-extended, even though both addr and a.base are unsigned types.

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

% 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 test test.c
%test

addr 0x80000000
addr 0x80000000

For a more detailed discussion of the conversion rules, refer to the ISO C standard. Also included in this standard are useful rules for ordinary arithmetic conversions and integer constants.

7.3.4 Pointer Arithmetic Instead of Integers

In general, using pointer arithmetic works better than integers because pointer arithmetic is independent of the data model, whereas integers might not be. Also, you can usually simplify your code by using pointer arithmetic. Consider the following example:

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

%
warning: conversion of pointer loses bits

Here is the modified version:

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

7.3.5 Structures

Check the internal data structures in an applications for holes. Use extra padding between fields in the structure to meet alignment requirements. This extra padding is allocated when long or pointer fields grow to 64 bits for the LP64 data-type model. In the 64-bit compilation environment on SPARC platforms, all types of structures are aligned to the size of the largest member within them. When you repack a structure, follow the simple rule of moving the long and pointer fields to the beginning of the structure. Consider the following structure definition:

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

Here is the same structure with the long and pointer data types defined at the beginning of the structure:

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

7.3.6 Unions

Be sure to check unions because their fields can change size between the ILP32 and the LP64 data-type models.

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

Here is the modified version

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

7.3.7 Type Constants

A lack of precision can cause the loss of data in some constant expressions. Be explicit when you specify the data types in your constant expression. Specify the type of each integer constant by adding some combination of {u,U,l,L}. You can also use casts to specify the type of a constant expression. Consider the following example:

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

Here is the modified version:

int i = 32;
long j = 1L << i;

7.3.8 Beware of Implicit Declarations

If you use -xc99=none, the C compiler assumes that any function or variable that is used in a module and not defined or declared externally is an integer. Any longs and pointers used in this way are truncated by the compiler’s implicit integer declaration. Place the appropriate extern declaration for the function or variable in a header and not in the C module. Include this header in any C module that uses the function or variable. If this is a function or variable defined by the system headers, you still need to include the proper header in the code. Consider the following example:

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

The proper headers are now in the modified version

#include <unistd.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
  char *name = getlogin();
  (void) printf("login = %s\n", name);
  return (0);
}

7.3.9 sizeof( ) Is an Unsigned long

In the LP64 data-type model, 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 integer. In some cases, this truncation causes loss of data.

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

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

7.3.11 Check Format String Conversion Operation

Make sure the format strings for printf(3S), sprintf(3S), scanf(3S), and sscanf(3S) can accommodate 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 compilation environments.

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

Here is the modified version

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.

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

Here is the modified version

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);