HP OpenVMS Systems

C++ Programming Language
Content starts here HP C++

HP C++
User's Guide for OpenVMS Systems


Previous Contents Index


Chapter 9
Using 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:


enum addr32_t (addr_32 }; 
enum addr64_t {addr_64 }; 
 
#pragma pointer_size short 
void *operator new(addr32_t, size_t s) { return _malloc32(s); } 
void *operator new[](addr32_t, size_t s) { return _malloc32(s); } 
 
#pragma pointer_size long 
void *operator new(addr64_t, size_t s) { return _malloc64(s); } 
void *operator new[](addr64_t, size_t s) { return _malloc64(s); } 

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:


#if defined (__INITIAL_POINTER_SIZE) /* Compiler supports 64-bit pointers */ 
#if __INITIAL_POINTER_SIZE > 0       /* Application uses 64-bit pointers */ 
#if __INITIAL_POINTER_SIZE == 32   /* Application uses some 64-bit pointers, 
                                       but default RTL routines are 32-bit.*/ 
 
#if __INITIAL_POINTER_SIZE == 64   /* Application uses 64-bit pointers and 
                                       default RTL routines are 64-bit. */ 

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:

  • The * in a declaration or cast:


    int **p;  // Declaration 
    ip = (int **)i;  // Cast 
    
  • The outer (leftmost) brackets [] in a formal parameter imply a *:


    void foo(int ia[10][20]) {}  
     
    // Means the following: 
     
    void foo(int (*ia)[20]) {}  
    
  • A function declarator as a formal parameter imply a *:


    void foo (int func()): 
     
    // Means the following: 
     
    void foo (int (*)() func); 
    
  • Any formal parameter of array or function type implies a *, even when bound in a typedef :


    typedef int a_type[10]; 
     
    void foo (a_type ia); 
     
    // Means the following: 
     
    void foo (int *ia); 
    

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:


    #pragma pointer_size 64 
     
    main(int argc, char **argv) 
      { ASSERT(sizeof(argv[0]) == 4); } 
    
  • A string literal produces a pointer based on the current pointer size when used as an r value :


    #pragma pointer_size 64 
     
    ASSERT(sizeof("x" + 0) == 8); 
     
    #pragma pointer_size 32 
     
    ASSERT(sizeof("x" + 0) == 4); 
    
  • The & operator yields a pointer based on the current pointer size unless it is applied to pointer dereference, in which case it is the size of the dereferenced pointer type:


    #pragma pointer_size 32 
    sizeof(&foo) == 32 
     
    #pragma pointer_size 64 
    sizeof(&foo) == 64 
     
    sizeof(&s ->next) == sizeof(s) 
    
  • The size of this pointer depends on the size in effect at the point of the member's signature definition, not on the use of the pointer.


    class foo { 
      public: 
        void f(); 
        void f2(); 
    }; 
     
    #pragma required_pointer_size short 
    void foo::f() 
    { sizeof(this)==4 } // this is short 
     
    #pragma required_pointer_size long 
    void foo::f2() 
    #pragma required_pointer_size short 
    { sizeof(this)==8; } // this is long 
    

9.3.2 Mixing Pointer Sizes

An application can use both 32-bit and 64-bit addresses. The following semantics apply when mixing pointers:

  • Assignments (including arguments) silently promote a 32-bit pointer rvalue to 64 bits if other type rules are met. Promotion means sign extension.
  • 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:

  1. Saves the current state of the member_alignment , extern_model , extern_prefix , and message pragmas.
  2. 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:

  1. 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.
  2. 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.
  3. The text of the prologue file is processed just before the text of the file specified by the #include .
  4. The text of the epilogue file is processed just after the text of the file specified by the #include.
  5. 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.

Suggested content for a prologue file is:


__DECC_INCLUDE_PROLOGUE.H: 
 
#ifdef __PRAGMA_ENVIRONMENT 
#pragma environment save 
#pragma environment header_defaults 
#else 
#error "__DECC_INCLUDE_PROLOGUE.H: This compiler does not support 
pragma environment" 
#endif 

Suggested content for an epilogue file is:


__DECC_INCLUDE_EPILOGUE.H: 
 
#ifdef __PRAGMA_ENVIRONMENT 
#pragma __environment restore 
#else 
#error "__DECC_INCLUDE_EPILOGUE.H: This compiler does not support 
pragma environment" 
#endif 

9.6 Avoiding Problems

Consider the following suggestions to avoid problems related to pointer size:

  • Write code to work with either 32-bit or 64-bit pointers.
  • Do bit manipulation on unsigned int and unsigned __int64 , and carefully cast pointers to and from them.
  • Heed compile-time warnings, using casts only where you are sure that pointers are not truncated.

9.7 Reasons for Not Using Mixed Pointer Sizes

Although HP C and C++ allow mixing pointer sizes, mixed pointers can cause certain types of error when used incorrectly. Consider the following examples:

  • Truncation


      #pragma pointer_size long 
      int *y=_malloc64();  // Y is a 64-bit pointer 
      #pragma pointer_size short 
      int *x=y;  // X is a 32-bit pointer, which results in truncation. 
    
  • Misread/miswrite


      int i,j; 
      #pragma pointer_size short 
      int *ptr=&i; 
      int **pptr=&ptr; 
      #pragma pointer_size long 
      int **lptr=pptr; 
             
      *lptr = &j;  // miswrite: 8 bytes write, but points to 4 byte ptr. 
      ptr = *lptr; // misread: 8 bytes read, but points to 4 byte ptr. 
    

Furthermore, the following C++ features discourage the use of mixed pointers:

  • Objects can allocate memory. Even if an object is in the 32-bit address space, the data contained in that object might not be.


    #pragma pointer_size long 
    class myObject { 
      char *myData; 
      public: 
         myObject() { myData = new char[1000]; } 
         ~myObject() { delete[] myData; } 
         char *getData() { return myData; } 
    }; 
           
    #pragma pointer_size short 
    myObject *ptr = new myObject(); //32-bit pointer to object in 32 bit space 
    char *data = ptr->getData(); //32-bit pointer truncated 64 bit pointer 
                                    to data in 64 bit space 
    
  • 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++.


    // C implementation of API 
    void API_f1(int); 
    #pragma pointer_size short 
    void API_f2(int *); 
    #pragma pointer_size long 
    void API_f2_64(int*); 
    void API_f3(int); 
          
    // C++ implementatin of API 
    class BASE { 
    public: 
      virtual void f1(int); 
    #pragma pointer_size short 
      virtual void f2(int *); 
    #pragma pointer_size long 
      virtual void f2_64(int*); 
    }; 
    class API : public BASE { 
    public: 
      virtual void f3(int); 
    } 
    
  • 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:


    struct FILE { 
      char *buffer; 
    }; 
           
    FILE *fopen(const char *,,,); 
    int fclose(FILE*); 
    

    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:


    x.cxx 
    ----- 
    #include <stdio.h> 
    #include <iostream> 
     
    #if !__INITIAL_POINTER_SIZE 
    #error this program should be compiled with /POINTER_SIZE qualifier 
    #endif 
     
    template <class T> 
    class short_pointer { 
    #pragma pointer_size save 
    #pragma pointer_size short 
        T* ptr; 
    public: 
        short_pointer(T* x) { ptr = x; } 
        operator T*() { return ptr; } 
        size_t get_ptr_size() { return sizeof(ptr); } 
    #pragma pointer_size restore 
    }; 
     
    template <class T> 
    class long_pointer { 
    #pragma pointer_size save 
    #pragma pointer_size long 
        T* ptr; 
    public: 
        long_pointer(T* x) { ptr = x; } 
        operator T*() { return ptr; } 
        size_t get_ptr_size() { return sizeof(ptr); } 
    #pragma pointer_size restore 
    }; 
     
    template<class T> 
    void func(short_pointer<T> x) { *x = 5; cout << x.get_ptr_size() << endl; } 
    template<class T> 
    void func(long_pointer<T> x)  { *x = 5; cout << x.get_ptr_size() << endl; } 
     
    int main() { 
    #pragma pointer_size short 
      func(short_pointer<int>((int*)_malloc32(sizeof(int)))); 
    #pragma pointer_size long 
      func(long_pointer<int>((int*)malloc(sizeof(int)))); 
    } 
     
    $ pipe cxx/pointer=short x.cxx ; cxxl x.obj ; run x.exe 
    4 
    8 
    $ pipe cxx/pointer=long x.cxx ; cxxl x.obj ; run x.exe 
    4 
    8 
    $ 
    


Previous Next Contents Index