HP OpenVMS Systems

HP C++User's Guide for OpenVMS Systems

Chapter 9Using 64-bit Address Space

This chapter describes 64-bit address support for the HP C++ compiler
on OpenVMS Alpha and I64 systems.

The introduction of 64-bit address space in OpenVMS greatly increases
the amount of memory available to applications. HP C++ has been
enhanced to permit use of this memory. The compiler provides a great
deal of flexibility about how this memory can be used. Conceptually,
this flexibility can be viewed as four models for development:

32-bit development

64-bit development

32-bit development with long pointers

64-bit development with short pointers

In a 32-bit development environment, all pointers are 32-bits long and
only 2 gigabytes of address space is available. This is the default and
was the only option that was available before this version of the
compiler. In a 64-bit development environment, all pointers are 64-bits
long and the address space is over a billion gigabytes.

Working in a homogeneous 32-bit or 64-bit environment is the prefered
and recommended way to do development. HP C++ for OpenVMS,
combined with the C Run-Time library, provide a seemless environment
for development. It should be possible for a well written, portable
program developed using 32-bit pointers to be recompiled and relinked
to use 64-bit pointers.

Because it is not always possible or desirable to work in a homogeneous
pointer environment. HP C++ supports mixed pointer sizes,
however, it requires greater care by developers. Some contexts where
heterogeneous pointer sizes might be used are:

Memory requirements of 32-bit application exceeds 2 gigabytes

Access to a legacy 32-bit library is required from a 64-bit
application

The memory foot print of a 64-bit application needs to be reduced

When the memory requirements of a 32-bit application begins to exceed 2
gigabytes, the most straight forward solution is to convert the
application to be a 64-bit application. Since practical considerations,
like the size of the application or the lack of source code for all
parts can prevent this, the alternative approach of isolating the use
of 64-bit pointers to a small portion of the application may be
preferable. In this situation, development would continue in the 32-bit
environment, using long pointers when necessary.

When doing 64-bit development, there are times when it becomes
necessary or desirable to use 32-bit pointers. The most common instance
is interfacing with a 32-bit library. Another is to save space, because
64-bit pointers consume twice as much memory as 32-bit pointers. In
this situtation, development could be done in a 64-bit environment,
using short pointers when necessary.

Limited empirical envidence suggests that using 32-bit pointers to save
space can reduce memory consumption by approximately 25% but at the
cost of greater complexity and the creation of potentially unneccessary
constraints in the application.

9.1 32-bit Versus 64-bit Development Environment

Besides pointer size, the following components of the development
environment determine whether it is a 32- or 64-bit environment:

Memory allocators

Libraries

Memory allocators control where in the address space memory is
allocated. Memory can be allocated in 32- or 64-bit space independent
of the pointer size. The default memory allocator is appropriate for
the development environment being used.

Libraries in a 32-bit environment expect pointers to be 32-bits and
memory to reside in the 32-bit address space, while libraries in the
64-bit environment expect pointers to be 64-bits. HP C++ for
OpenVMS ships with two libraries: one for the 32-bit environment and
one for the 64-bit environment. In addition to supporting the 64-bit
environment, the second library also supports the new object model
refered to as model ANSI.

Caution

When compiling /POINTER_SIZE=LONG, the STL template classes (such as
string, and set, map) can be used only when /MODEL=ANSI is specified.

The C Runtime is a single library that supports both environments. See
the HP C Run-Time Library Reference Manual for OpenVMS Systems for information about how support for both
environments was achieved with a single library. See Section 9.6 for
a discussion of why it is difficult to produce a single C++ library to
support both environments.

9.1.1 Model ANSI

The new ANSI object model allows the compiler to better conform to the
ANSI/ISO C++ standard while providing the 64-bit development
environment. This object model is specified using the /MODEL=ANSI
compiler and link options. To build a 64-bit application using the ANSI
object model, you enter commands in the following format:

$ cxx /model=ansi filename.cxx
$ cxxlink/model=ansi filename

Caution

The new ANSI object model is not compatible with the old object model.
You must compile and link your entire application with one model or the
other.

9.1.2 Memory Allocators

In C++, the primary memory allocator is
new
. Use of the default allocators causes memory to be allocated that is
appropriate for the default pointer size for the module (not the
current pointer size). Specialized placement-new allocators can be used
to control where an object is allocated. The header
newext.hxx
contains the following definitions:

Use of the allocators from the C Run Time is also possible. You can
select a specific C allocator by adding a prefix underbar and either 32
or 64 as a suffix.

Function

32-bit

64-bit

malloc

_malloc32

_malloc64

calloc

_calloc32

_calloc64

realloc

_realloc32

_realloc64

strdup

_strdup32

_strdup64

When attempting to mix pointer sizes in your program, distinguish
between the concepts of pointer size and memory allocators. The pointer
size dictates the maximum amount of address space a pointer can
reference, while the allocator controls the where the memory will be
allocated.

A library implemented with 64-bit pointers that uses only a 32-bit
allocator can with care be used by an application that uses 32-bit
pointers. If the library uses a 64-bit allocator, the application
cannot reference any pointers returned. To a large extent, it is the
memory allocator, not the pointer size, that determines
interoperability.

9.1.3 64-bit Pointer Support in the C Run Time Library

In addition to allocators, other functions in the C Run Time Library,
such as
strcpy
, are affected by pointer size. As with the alloators, the C++ compiler
calls a version of the routine is for the development environment. See
the HP C Run-Time Library Reference Manual for OpenVMS Systems for more details.

9.2 Qualifiers and Pragmas

The following qualifiers, pragmas, and predefined macros control
pointer size:

/MODEL=ANSI

/[NO]POINTER_SIZE={LONG | SHORT | 64 |32}

#pragma pointer_size

#pragma required_pointer_size

#pragma environment cxx_header_defaults

__INITIAL_POINTER_SIZE
predefined macro

9.2.1 The /MODEL=ANSI Qualifier

The /MODEL=ANSI qualifier enables the new ANSI object model. This model
implies /POINTER_SIZE=LONG in addition to supporting new C++ constructs
that could not be supported in the object object model designed to
support the ARM definition of the language. This option must be
specified during compilation and linking.

9.2.2 The /POINTER_SIZE Qualifier

The /POINTER_SIZE qualifier lets you specify a value of 64 or 32 (or
LONG or SHORT) as the default pointer size within the compilation unit.
You can compile one set of modules using 32-bit pointers and another
set using 64-bit pointers. Take care when these two separate groups of
modules call each other.

The default is /NOPOINTER_SIZE, which has the following effects:

Disables pointer-size features, such as the ability to use
#pragma pointer_size

Directs the compiler to assume that all pointers are 32-bit pointers

This default represents no change from previous versions of
HP C++.

Specifying /POINTER_SIZE with a keyword value (32, 64, SHORT, or LONG)
has the following effects:

Enables processing of
#pragma pointer_size
.

Sets the initial default pointer size to 32 or 64, as specified.

Predefines the preprocessor macro
__INITIAL_POINTER_SIZE
to 32 or 64, as specified. If /POINTER_SIZE is omitted from the command
line,
__INITIAL_POINTER_SIZE
is 0, which allows you to use
#ifdef __INITIAL_POINTER_SIZE
to test whether the compiler supports 64-bit pointers.

For /POINTER_SIZE=64, the HP C RTL name mapping table is changed to
select the 64-bit versions of
malloc
,
calloc
, and other RTL routines by default.

Use of the /POINTER_SIZE qualifier also influences the processing of HP
C RTL header files:

For those functions that have both 32-bit and 64-bit
implementations, specifying /POINTER_SIZE enables function prototypes
to access both functions, regardless of the actual value supplied to
the qualifier. The value specified to the qualifier determines the
default implementation to call during that compilation unit.

Functions that require a second interface to be used with 64-bit
pointers reside in the same object libraries and shareable images as
their 32-bit counterparts. Because no new object libraries or shareable
images are introduced, using 64-bit pointers does not require changes
to your link command or link options files.

See the HP C Run-Time Library Reference Manual for OpenVMS Systems for more information on the impact of 64-bit
pointer support on HP C++ RTL functions.

9.2.3 The __INITIAL_POINTER_SIZE Macro

The
__INITIAL_POINTER_SIZE
preprocessor macro is useful for header-file authors to determine:

Whether the compiler supports 64-bit pointers.

Whether the application expects to use 64-bit pointers.

Header-file code can then be conditionalized using the following
preprocessor directives:

9.2.4 Pragmas

The
#pragma pointer_size
and
#pragma required_pointer_size
preprocessor directives can be used to change the pointer size
currently in effect within a compilation unit. You can default pointers
to 32-bits and then declare specific pointers within the module as
64-bits. In this case, you also need to specifically call the
appropriate allocator to obtain memory from the 64-bit memory area.

These pragmas have the following format:

#pragma pointer_size keyword
#pragma required_pointer_size keyword

The keyword is one of the following:

{
short
|32}

32-bit pointer

{
long
|64}

64-bit pointer

save

Saves the current pointer size

restore

Restores the current pointer size to its last saved state

The
#pragma pointer_size
and
#pragma required_pointer_size
directives work essentially the same way, except that
#pragma required_pointer_size
always takes effect regardless of command-line qualifiers, while
#pragma pointer_size
is in effect only when the /POINTER_SIZE command-line qualifier is used.

By changing the command-line qualifier,
#pragma pointer_size
allows a program to be built using 64-bit features as purely as a
32-bit program.

The
#pragma required_pointer_size
is intended for use in header files where interfaces to system data
structures must use a specific pointer size regardless of how the
program is compiled.

An alternative to controling the pointer size is
#pragma environment
. This pragma controls all compiler states that include pointer size.
This pragma is fully documented in Section 2.1.1.3. The primary change
for support of long pointers is the addition of a new
cxx_header_defaults keyword.

This new keyword is similar to the header_defaults
keyword, but differs in the effect on
pointer_size
. With header_defaults,
pointer_size
is made short, while with cxx_header_defaults, the
pointer_size depends on the model being used. When developing in model
ANSI, the
pointer_size
is 64 bits; in model ARM (the default), it is 32 bits.

9.3 Determining Pointer Size

The pointer-size qualifiers and pragmas affect only a limited number of
constructs in the C++ language itself. At places where the syntax
creates a pointer type, the pointer-size context determines the size of
that type. Pointer-size context is defined by the most recent pragma
(or command-line qualifier) affecting pointer size.

Here are examples of places in the syntax where a pointer type is
created:

9.3.1 Special Cases

The following special cases are not affected by pointer-size context:

Formal parameters to
main
are always treated as if they were in a
#pragma pointer_size system_default
context, which is 32-bit pointers for OpenVMS systems. For example,
regardless of the
#pragma pointer_size 64
directive, argv[0] is a 32-bit pointer:

A warning is issued for an assignment of a 64-bit rvalue to a
32-bit lvalue (without an explicit cast).

For purposes of type compatibility, a different size pointer is a
different type (for example, when matching a prototype to a definition,
or other contexts involving redeclaration), however, overloading is not
permitted.

The debugger knows the difference between pointers of different
sizes.

9.4 Header File Considerations

Take note of the following general header-file considerations:

Header files usually define interfaces with types that must match
the layout used in library modules.

Header files often do not bind "top-level" pointer types.
Consider, for example:

fprintf(FILE *, const char *, ...);

A "FILE * fp;" in a declaration in a different area of source code
might be a different size.

All pointer parameters occupy 64 bits in the calling sequence, so a
top-level mismatch of this kind is acceptable if the called function
does not lose the high bits internally.

Routines dealing with pointers to pointers (or data structures
containing pointers) cannot be enabled to work simply by passing them
both 32-bit and 64-bit pointers. You need separate 32-bit and 64-bit
variants of the routine.

Be aware that pointer-size controls are not unique in the way they
affect header files; other features that affect data layout have
similar impact. For example, most header files should be compiled with
32-bit pointers regardless of pointer-size context. Also, most system
header files must be compiled with
member_alignment
regardless of user pragmas or qualifiers.

To address this issue more generally, you can use the
pragma environment
directive to save context and set header defaults at the beginning of
each header file, and then to restore context at the end. See
Section 2.1.1.3 for a description of
pragma environment
.

For header files that have not yet been upgraded to use
#pragma environment
, the /POINTER_SIZE=64 qualifier can be difficult to use effectively.
For such header files, the compiler automatically applies user-defined
prologue and epilogue files before and after the text of the included
header file. See Section 9.5 for more information on
prologue/epilogue files.

9.5 Prologue/Epilogue Files

HP C++ automatically processes user-supplied prologue and
epilogue header files. This feature is an aid to using header files
that are not 64-bit aware within an application that is built to
exploit 64-bit addressing.

9.5.1 Rationale

HP C++ header files typically contain a section at the top that:

Saves the current state of the
member_alignment
,
extern_model
,
extern_prefix
, and
message
pragmas.

Sets these pragmas to the default values for the system.

A section at the end of the header file then restores these pragmas to
their previously-saved state.

Mixed pointer sizes introduce another kind of state that typically
needs to be saved, set, and restored in header files that define fixed
32-bit interfaces to libraries and data structures.

The
#pragma environment
preprocessor directive allows headers to control all compiler states
(message suppression,
extern_model
,
member_alignment
, and
pointer_size
) with one directive.

However, for header files that have not yet been upgraded to use
#pragma environment
, the /POINTER_SIZE=64 qualifier can be difficult to use effectively.
In this case, the automatic mechanism to include prologue/epilogue
files allows you to protect all of the header files within a single
directory (or modules within a single text library). You do this by
copying two short files into each directory or library that needs it,
without having to edit each header file or library module separately.

In time, you should modify header files to either exploit 64-bit
addressing (like the HP C RTL), or to protect themselves with
#pragma environment
. Prologue/epilogue processing can ease this transition.

9.5.2 Using Prologue/Epilogue Files

Prologue/epilogue file are processed in the following way:

When the compiler encounters an
#include
preprocessing directive, it determines the location of the file or text
library module to be included. It then checks to see if one or both of
the two following specially named files or modules exist in the same
location as the included file:

__DECC_INCLUDE_PROLOGUE.H
__DECC_INCLUDE_EPILOGUE.H

The location is the OpenVMS directory containing the included file
or the text library file containing the included module. (In the case
of a text library, the .h is stripped off.) The directory is the
result of using the $PARSE/$SEARCH system services with concealed
device name logicals translated. Therefore, if an included file is
found through a concealed device logical that hides a search list, the
check for prologue/epilogue files is still specific to the individual
directories making up the search list.

If the prologue and epilogue files do exist in the same location as
the included file, then the content of each is read into memory.

The text of the prologue file is processed just before the
text of the file specified by the
#include
.

The text of the epilogue file is processed just after the
text of the file specified by the #include.

Subsequent
#includes
that refer to files from the same location use the saved text from any
prologue/epilogue file found there.

The prologue/epilogue files are otherwise treated as if they had been
included explicitly:
#line
directives are generated for them if /PREPROCESS_ONLY output is
produced, and they appear as dependencies if /MMS_DEPENDENCY output is
produced.

To take advantage of prologue/epilogue processing for included header
files, you need to create two files,
__DECC_INCLUDE_PROLOGUE.H
and
__DECC_INCLUDE_EPILOGUE.H
, in the same directory as the included file.

Virtual functions make it difficult to maintain backward
compatibility. Consider the following two implementations of an
interface called API. One is written in C, the other in C++. With the C
implementation, you can add the new entry with the new pointer size in
an upwardly compatible way. In C++, you cannot do so because the
functions are virtual. Adding a virtual function to a class breaks
backward compatibility. Granted, the C++ interface provides
polymorphism that is not available in the C interface, but the
availability of this feature is one of the reasons why applications are
designed using C++.

Polymorphism semantics are difficult to define. It is easy to
imagine overloading while working with mixed 32/64 bit pointers when
the parameter is a simple pointer: the pointers are simply different
types. However, if the pointer is embedded in a structure, how are
these structures differentiated? Consider the following code fragment:

It is easy to consider tagging the structure with a flag to
indicate whether it is long or short, but it is possible for a
structure to have more than one pointer definition. In that case, there
could be 2^n different versions of the
struct
. To avoid these issues, the C++ compiler treats 32 and 64 bit pointers
as the same type. If you want to treat pointers as different based on
size, use template classes: