[an error occurred while processing this directive]

HP OpenVMS Systems

C Programming Language
Content starts here HP C

HP C
User's Guide for OpenVMS Systems


Previous Contents Index

1.7 64-bit Addressing Support

OpenVMS 64-bit virtual addressing support makes the 64-bit virtual address space defined by the Alpha and Itanium architectures available to the OpenVMS operating system and its users. It also allows per-process virtual addressing for accessing dynamically mapped data beyond traditional 32-bit limits.

The HP C compiler supports 64-bit pointers on all hardware platforms where the OpenVMS operating system supports 64-bit pointers; that is, on the Alpha and Itanium processors.

This support is provided through command-line qualifiers and pragma preprocessor directives that control the size of the C pointer because:

  • Typical C usage involves many objects accessed through pointers rather than single monolithic arrays or structures.
  • Huge declared objects would have an impact on object-module format and the linker.

    Note

    Single objects larger than 2 gigabytes are not fully supported, even with 64-bit virtual addressing in effect.
  • Minimal source-code edits are required to exploit the 64-bit space where needed. Because the pragmas affect a region of source code, it is not necessary to modify every declaration.

No changes are required for existing 32-bit applications that do not need to exploit 64-bit addressing.

1.7.1 Qualifiers and Pragmas

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

  • /[NO]POINTER_SIZE={LONG | SHORT | 64 |32}
  • /[NO]CHECK=[NO]POINTER_SIZE=(option,...)
  • #pragma pointer_size
  • #pragma required_pointer_size
  • __INITIAL_POINTER_SIZE predefined macro

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

  • 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 over 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 or not 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.

See Section 1.3.4 for more information about /POINTER_SIZE.

1.7.1.2 The __INITIAL_POINTER_SIZE Macro

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

  • If the compiler supports 64-bit pointers.
  • If 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. */ 

1.7.1.3 The /CHECK=POINTER_SIZE Qualifier

Use the /CHECK=POINTER_SIZE qualifier to generate code that checks 64-bit pointer values at runtime to make sure they can fit in a 32-bit pointer. If such a value cannot be represented by a 32-bit pointer, the run-time code signals a range error (SS$_RANGEERR).

Be aware that the compiler generates the same kinds of warning messages for pointer-size mismatches whether or not this qualifier is specified. The run-time checks can detect problems that cannot be detected at compile time, and can help determine whether or not certain warnings are safe to suppress.

See Section 1.3.4 for more information about /CHECK=POINTER_SIZE, including defaults and an example.

1.7.1.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 _malloc64 form of malloc 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 only in effect when the /POINTER_SIZE command-line qualifier is used.

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

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.

See Sections 5.4.19 and 5.4.20 for more information on the pointer-size pragmas.

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

Note that a typedef binds the meaning of pointer syntax while a macro does not. Even though both constructs can contain a * used in a declaration, the * in the macro definition is not affected by any pointer-size controls until the point at which the macro is expanded. For example:


#pragma pointer_size 64 
typedef int * j_ptr;    // * is 64-bit 
#define J_PTR int *     // * is not analyzed 
 
#pragma pointer_size 32 
j_ptr j;   // j is a 64-bit pointer. 
J_PTR J;   // J is a 32-bit pointer. 

1.7.2.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 32-bit pointer when used as an rvalue:


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


    sizeof(&foo) == 32 
     
    sizeof(&s ->next) == sizeof(s) 
    
  • An rvalue cast to a 32-bit pointer type does not modify the high-order 32 bits of a 64-bit operand. sizeof yields 4 bytes, but the high bits are not lost unless a 4-byte assignment occurs:


    #pragma pointer_size 64 
    typedef int * ip64; 
     
    #pragma pointer_size 32 
    typedef int * ip32; 
     
    ip64 a,b; 
    ip32 c; 
     
    a = (ip32)b;   // No high-order bits are lost 
    c = (ip32)b;   // High-order bits are lost 
    

1.7.2.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).
  • The debugger knows the difference between pointers of different sizes.

1.7.3 Header File Considerations

The following general header-file considerations should be kept in mind:

  • 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 OpenVMS Alpha and I64 calling sequence, so a top-level mismatch of this kind is all right 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 to have separate 32-bit and 64-bit variants of the routine.
  • The HP C RTL header files and the compiler cooperatively provide dual implementations of functions that need to know the pointer size used by the caller. They have different names. The compiler automatically calls the appropriate name within the pointer-size context if the source code calls the simple name. For example, a call to malloc becomes:
    • _malloc64 if /POINTER_SIZE=64.
    • _malloc32 if /POINTER_SIZE=32.
    • malloc if /POINTER_SIZE is omitted.

    If /POINTER_SIZE is specified alone or with a value, _malloc64 or _malloc32 can be called explicitly. If /POINTER_SIZE is not specified, the program is compiled to be unaware of 64-bit pointers, and so the declarations of these alternate variants are suppressed.

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 (on OpenVMS Alpha and I64 systems) must be compiled with member_alignment regardless of user pragmas or qualifiers.

To address this issue more generally, the pragma environment directive can be used to save context and set header defaults at the beginning of each header file, and then to restore context at the end. See Section 5.4.4 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 that are not 64-bit aware, the compiler automatically applies user-defined prologue and epilogue files before and after the text of the included header file. See Section 1.7.4 for more information on prologue/epilogue files.

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

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

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

1.7.5 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 by using only the /POINTER_SIZE qualifier.
  • 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.
  • Enable the optional compile-time warning (/WARN=ENABLE=MAYHIDELOSS).
  • Do thorough testing when compiling with /CHECK=POINTER_SIZE.

1.7.6 Examples

The following examples illustrate the use and misuse of 64-bit pointers.

Example 1-2 Watch Out for Pointers to Pointers (**)

/* CC/NAME=AS_IS/POINTER_SIZE=64 */ 
 
#include <stdio.h> 
 
#pragma pointer_size 64 
char  *C[2]    = {"AB", "CD"};  /* sizeof(C) = 16            */ 
char **CPTRPTR = C; 
char **CPTR; 
 
#pragma pointer_size 32 
char  *c[2]    = {"ab", "cd"};  /* sizeof(C) = 8             */ 
char **cptrptr = c; 
char **cptr; 
 
int main (void) 
{ 
    CPTR    =  cptr;           /* No problem.               */ 
    cptr    =  CPTR;           /* %CC-W-MAYLOSEDATA         */ 
 
    CPTRPTR = cptrptr;         /* %CC-W-PTRMISMATCH         */ 
    cptrptr = CPTRPTR;         /* MAYLOSEDATA & PTRMISMATCH */ 
    puts(cptrptr[0]);          /* ab                        */ 
    puts(cptrptr[1]);          /* cd                        */ 
    puts(CPTRPTR[0]);          /* Bad address passed.       */ 
    puts(CPTRPTR[1]);          /* Fetch off end of c.       */ 
} 

Compiling Example 1-2 produces:


$ cc example1/name=as_is/pointer_size 
    cptr    =  CPTR;           /* %CC-W-MAYLOSEDATA         */ 
....^ 
%CC-W-MAYLOSEDATA, In this statement, "CPTR" has a larger 
data size than "short pointer to char".  Assignment may 
result in data loss.) 
 
    CPTRPTR = cptrptr;         /* %CC-W-PTRMISMATCH         */ 
....^ 
%CC-W-PTRMISMATCH, In this statement, the referenced type 
of the pointer value "cptrptr" is "short pointer to char", 
which is not compatible with "long pointer to char". 
 
    cptrptr = CPTRPTR;         /* MAYLOSEDATA & PTRMISMATCH */ 
....^ 
%CC-W-MAYLOSEDATA, In this statement, "CPTRPTR" has a 
larger data size than "short pointer to short pointer 
to char".  Assignment may result in data loss.) 
 
    cptrptr = CPTRPTR;         /* MAYLOSEDATA & PTRMISMATCH */ 
....^ 
%CC-W-PTRMISMATCH, In this statement, the referenced type 
of the pointer value "CPTRPTR" is "long pointer to char", 
which is not compatible with "short pointer to char". 

Example 1-3 Trivial 64-Bit Exploitation

#include <stdio.h> 
#include <stdlib.h> 
__int64 limit, count; 
size_t bytes; 
char  *cp, *prevcp; 
 
int main(int argc, char **argv) 
{ 
    sscanf(argv[1], "%d",  &bytes); 
    sscanf(argv[2], "%Ld", &limit); 
    printf("bytes %d, limit %Ld, tot %Ld\n", 
           bytes,     limit, bytes * limit); 
    for (count=0; count < limit; count++)  { 
        if (!(cp = malloc(bytes)))  { 
           printf("Max %Ld bytes.\n", bytes * (count + 1)); 
           break; 
    } else if (!prevcp) 
        printf("First addr %Lx.\n", cp); 
    } 
    prevcp = cp; 
    printf("Last addr %Lx.\n", prevcp); 
} 

Compiling, linking, and running Example 1-3 produces:


$ cc example2 
$ link example2 
 
$ example2:==$sys$login:[.john]example2 ! << set up a symbol 
$ example2 65536 1234567890123456 
bytes 65536, limit 1234567890123456, tot 7121664952292605952 
First addr 610b0. 
First addr 730b0. 
First addr 850b0. 
First addr 970b0. 
First addr a90b0. 
   . 
   . 
   . 
First addr f1c30b0. 
First addr f1d50b0. 
First addr f1e70b0. 
First addr f1f90b0. 
First addr f20b0b0. 
Max 225378304 bytes. 
Last addr 0. 
 
$ 
$ cc/pointer_size=64 example2 
$ link example2 
$ example2 65536 1234567890123456 
bytes 65536, limit 1234567890123456, tot 
7121664952292605952 
First addr 1c0010010. 
Max 42532864 bytes. 
Last addr 1c2d8e010. 

Example 1-4 Preceding Example No Longer Trivial

#include <stdio.h> 
#include <stdlib.h> 
__int64 limit, count; 
size_t bytes; 
char  *cp, *prevcp; 
 
static void do_args(char **args) 
{ 
    sscanf(argv[1], "%d",  &bytes); 
    sscanf(argv[2], "%Ld", &limit); 
    printf("bytes %d, limit %Ld, tot %Ld\n", 
           bytes,     limit, bytes * limit); 
} 
 
 
int main(int argc, char **argv) 
{ 
    do_args(argv); 
    for (count=0; count < limit; count++)  { 
        if (!(cp = malloc(bytes)))  { 
           printf("Max %Ld bytes.\n", bytes * (count + 1)); 
           break; 
    } else if (!prevcp) { 
        printf("First addr %Lx.\n", cp); 
    } 
    prevcp = cp; 
    printf("Last addr %Lx.\n", prevcp); 
} 

Compiling Example 1-4 produces:


$ cc/pointer_size=64 example3 
do_args(argv); 
....^ 
%CC-W-PTRMISMATCH, In this statement, the referenced type 
of the pointer value "argv" is "short pointer to char", 
which is not compatible with "long pointer to char". 


Previous Next Contents Index