HP OpenVMS SystemsC++ Programming Language |
HP C++
|
Previous | Contents | Index |
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:
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:
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.
Besides pointer size, the following components of the development environment determine whether it is a 32- or 64-bit environment:
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.
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.
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 |
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. |
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.
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.
The following qualifiers, pragmas, and predefined macros control pointer size:
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.
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:
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:
Use of the /POINTER_SIZE qualifier also influences the processing of HP C RTL header 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.
The __INITIAL_POINTER_SIZE preprocessor macro is useful for header-file authors to determine:
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. */ |
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.
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:
int **p; // Declaration ip = (int **)i; // Cast |
void foo(int ia[10][20]) {} // Means the following: void foo(int (*ia)[20]) {} |
void foo (int func()): // Means the following: void foo (int (*)() func); |
typedef int a_type[10]; void foo (a_type ia); // Means the following: void foo (int *ia); |
The following special cases are not affected by pointer-size context:
#pragma pointer_size 64 main(int argc, char **argv) { ASSERT(sizeof(argv[0]) == 4); } |
#pragma pointer_size 64 ASSERT(sizeof("x" + 0) == 8); #pragma pointer_size 32 ASSERT(sizeof("x" + 0) == 4); |
#pragma pointer_size 32 sizeof(&foo) == 32 #pragma pointer_size 64 sizeof(&foo) == 64 sizeof(&s ->next) == sizeof(s) |
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 |
An application can use both 32-bit and 64-bit addresses. The following semantics apply when mixing pointers:
Take note of the following general header-file considerations:
fprintf(FILE *, const char *, ...); |
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.
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.
HP C++ header files typically contain a section at the top that:
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.
Prologue/epilogue file are processed in the following way:
__DECC_INCLUDE_PROLOGUE.H __DECC_INCLUDE_EPILOGUE.H |
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 |
Consider the following suggestions to avoid problems related to pointer size:
Although HP C and C++ allow mixing pointer sizes, mixed pointers can cause certain types of error when used incorrectly. Consider the following examples:
#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. |
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:
#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 |
// 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); } |
struct FILE { char *buffer; }; FILE *fopen(const char *,,,); int fclose(FILE*); |
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 |