DEC C
User's Guide for OpenVMS Systems


Previous Contents Index

1.7 64-bit Addressing Support (ALPHA ONLY)

OpenVMS Alpha 64-bit virtual addressing support makes the 64-bit virtual address space defined by the Alpha architecture 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.

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

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:

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:

This default represents no change over previous versions of DEC C.

Specifying /POINTER_SIZE alone (with no value) directs the compiler to assume that all pointers are 32-bit pointers.

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

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

See the DEC C Run-Time Library Reference Manual for OpenVMS Systems for more information on the impact of 64-bit pointer support on DEC 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:

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


#if defined (<double_uscore>INITIAL_POINTER_SIZE) /* Compiler supports 64-bit pointers */ 
#if <double_uscore>INITIAL_POINTER_SIZE > 0       /* Application uses 64-bit pointers */ 
#if <double_uscore>INITIAL_POINTER_SIZE == 32   /* Application uses some 64-bit pointers, 
                                       but default RTL routines are 32-bit.*/ 
 
#if <double_uscore>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 most useful 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.14 and 5.4.15 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:

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:

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:

1.7.3 Header File Considerations

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

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

DEC 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

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


    <double_uscore>DECC_INCLUDE_PROLOGUE.H 
    <double_uscore>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:


<double_uscore>DECC_INCLUDE_PROLOGUE.H: 
 
#ifdef <double_uscore>PRAGMA_ENVIRONMENT 
#pragma environment save 
#pragma environment header_defaults 
#else 
#error "<double_uscore>DECC_INCLUDE_PROLOGUE.H: This compiler does not support 
pragma environment" 
#endif 

Suggested content for an epilogue file is:


<double_uscore>DECC_INCLUDE_EPILOGUE.H: 
 
#ifdef <double_uscore>PRAGMA_ENVIRONMENT 
#pragma <double_uscore>environment restore 
#else 
#error "<double_uscore>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:

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 */ 
 
#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> 
<double_uscore>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 65536 1234567890123456 
bytes 65536, limit 1234567890123456, tot 
7121664952292605952 
First addr 226008 
Max 42663936 bytes. 
Last addr 2fc8008. 
$ 
$ 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> 
<double_uscore>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