Jump to 
content
HP.com Home Products and Services Support and Drivers Solutions How to Buy
»  Contact HP

 

HP C++

HP C++
Using HP C++ for Tru64 UNIX and Linux Alpha


Previous Contents Index


Chapter 2
HP C++ Implementation

This chapter discusses the features and characteristics specific to the HP C++ implementation, including pragmas, predefined names, numerical limits, and other implementation-dependent aspects of the language definition.

2.1 Implementation-Specific Attributes

This section describes pragmas, predefined names, and limits placed on the number of characters and arguments used in HP C++ programs.

2.1.1 #pragma Preprocessor Directive

The #pragma preprocessor directive is a standard method for implementing features that differ from one compiler to the next. This section describes pragmas specifically implemented in the compiler for HP Tru64 UNIX and Linux Alpha systems.

Note that the compiler accepts double underscore forms of keywords (for example, #pragma __inline ) for compatibility with user-written header files.

Although certain #pragma directives are subject to macro expansion, not all of those new to the language in this and later releases are subject to such expansion. Therefore, users should not write programs that rely on macro expansion of the newer pragmas.

The following #pragma directives are subject to macro expansion. A macro reference can occur anywhere after the pragma keyword.
builtins inline linkage 2 use_linkage 2
dictionary 1 noinline module extern_model
member_alignment message define_template extern_prefix


1Not supported; specific to C on OpenVMS
2Not supported; specific to C

2.1.1.1 #pragma define_template Directive

The #pragma define_template preprocessor directive instructs the compiler to define a template with the arguments specified in the pragma. This pragma has the following syntax:

#pragma define_template name < template-argument-list >

For example, the following statement instructs the compiler to define the template mytempl with the arguments arg1 and arg2 :


#pragma define_template mytempl<arg1, arg2> 

For more information on how to use templates with the #pragma
define_template directive, see Section 5.4.

2.1.1.2 #pragma instantiate Directive

The compiler provides several other pragmas that provide finer control over the instantiation process. Instantiation pragmas can be used to control the instantiation of specific template entities or sets of template entities. There are two instantiation pragmas:

  • The instantiate pragma causes a specified entity to be instantiated, similar to the define_template pragma. It provides finer instantiation control than define_template when instantiating function templates. It provides the same functionality as the explicit instantiation syntax described in Section 14.7.2 of the C++ International Standard.
  • The do_not_instantiate pragma suppresses the instantiation of a specified entity. It is typically used to suppress the instantiation of an entity for which a specific definition will be supplied.

For more information on how to use templates with the #pragma instantiate directive, see Section 5.4.

2.1.1.3 #pragma environment Directive

The #pragma environment preprocessor directive offers a way to single-handedly set, save, or restore the states of context pragmas. This directive protects include files from contexts set by encompassing programs and protects encompassing programs from contexts that could be set in header files that they include.

On HP Tru64 UNIX and Linux Alpha systems, the #pragma environment directive affects the following pragmas:

#pragma member_alignment/pack
#pragma message
#pragma extern_model
[Tru64] #pragma pointer_size/required_pointer_size
#pragma required_vptr_size

This pragma has the following syntax:

#pragma environment command_line
#pragma environment header_defaults
#pragma environment restore
#pragma environment save

command_line

Sets, as specified on the command line, the states of all the context pragmas. You can use this pragma to protect header files from environment pragmas that take effect before the header file is included.

header_defaults

Sets the states of all the context pragmas to their default values for HP Tru64 UNIX and Linux Alpha systems. This is almost equivalent to the situation in which a program with no command-line options and no pragmas is compiled; except that this pragma sets the pragma message state to #pragma nostandard , as is appropriate for system header files.

restore

Restores the current state of every pragma that has an associated context.

save

Saves the current state of every pragma that has an associated context.

Someone who creates a general purpose library, distributed as an archive or shared library, typically distributes header files that specify the interface to the library. For calls to the library to work, the header file must normally use the same member alignment, pointer size, extern model, and other compilation options as when the library was built.

The header file should contain pragmas to save the user's compilation options, set the correct compilation options for the library, and then restore the user's compilation options when the include file ends. The pragmas let library users choose whatever compilation options are appropriate for their own code without unintentionally affecting the interface to the library.

Not only is the #pragma environment preprocessor directive more convenient than explicitly specifying each individual pragma that controls a compilation option, it is also upwardly compatible. As new pragmas are added to the compiler , #pragma environment header_defaults will be enhanced to set the state of the new pragmas to their default. Thus, a header file that uses #pragma environment sets a known state for not only the current pragmas in the compiler, but future pragmas as well.

Without requiring further changes to the source code, you can use #pragma environment to protect header files from things like language extensions and enhancements that might introduce additional contexts.

A header file can selectively inherit the state of a pragma from the including file and then use additional pragmas as needed to set the compilation to nondefault states. For example:


#ifdef __PRAGMA_ENVIRONMENT 
#pragma __environment save  (1)
#pragma __environment header_defaults (2)
#pragma member_alignment restore (3)
#pragma member_alignment save (4)
#endif 
. 
.  /* contents of header file */ 
. 
#ifdef __PRAGMA_ENVIRONMENT 
#pragma __environment restore 
#endif 

In this example:

  1. Saves the state of all context pragmas
  2. Sets the default compilation environment
  3. Pops the member alignment context from the #pragma member_alignment stack that was pushed by #pragma __environment save (restoring the member alignment context to its preexisting state)
  4. Pushes the member alignment context back onto the stack so that the #pragma __environment restore can pop off the entry

Thus, the header file is protected from all pragmas, except for the member alignment context that the header file was meant to inherit.

2.1.1.4 #pragma extern_prefix Directive

The #pragma extern_prefix directive is an OpenVMS pragma that cannot be used on HP Tru64 UNIX and Linux Alpha systems.

2.1.1.5 #pragma ident Directive

The #pragma ident directive enables insertion of an identification string into the object and executable (usually a version number). The string can be found by using the strings command or, if the string contains a specific recognized substring, by using the what command. For more information on the strings and what commands, see the reference pages.

In version 4.n of HP's HP Tru64 UNIX, the contents of the string are placed in the .rconst section of the object file and in the executable. In the next major release of the operating system, the contents of the string are placed in the .comment section of the object and the executable. The .comment section is present in the executable on disk but is not mapped into virtural memory at load-time.

The mcs command allows users to perform operations on the comment section .comment of (e)COFF object files. This section can contain information such as the "ident" string from a source file and other information used by components of the HP Tru64 UNIX development environment. Users can optionally add their own information to the comment section by using the mcs tool.

For additional information, refer to the Revision Contol System (RCS) and Source Code Control System (SCCS) reference pages.

The pragma has the following syntax:

#pragma ident "user-defined string"

2.1.1.6 #pragma [no]inline Directive

This is a C pragma that cannot be used in C++. Use the inline keyword instead.

2.1.1.7 #pragma intrinsic Directive

The #pragma intrinsic directive specifies that calls to the specified functions are intrinsic. Intrinsic functions are functions in which the compiler generates optimize code in certain situations, possibly avoiding a function call.

The #pragma intrinsic directive has the following syntax:

#pragma intrinsic (function1[,function2, ...])

You can use this directive to make intrinsic the default form of functions that have intrinsic forms. The following functions have intrinsic forms:


abs 
fabs 
labs 
alloca 

You can use the #pragma function directive to override the #pragma intrinsic directive for specified functions.

The function must have a declaration visable at the time the compiler encounters the #pragma intrinsic directive. The compiler takes no action if the compiler does not recognize the specified function name as an intrinsic.

2.1.1.8 #pragma [no]member_alignment Directive

By default, the compiler aligns structure members so that members are stored on the next boundary appropriate to the type of the member; that is, bytes are on the next byte boundary, words are on the next word boundary, and so on.

You can use the #pragma member_alignment preprocessor directive to explicitly specify member alignment. For example, using #pragma member_alignment aligns a long variable on the next longword boundary, and it aligns a short variable on the next word boundary.

Using #pragma nomember_alignment causes the compiler to align structure members on the next byte boundary regardless of the type of the member. The only exception to this is for bit-field members.

If used, the nomember_alignment pragma remains in effect until the compiler encounters the member_alignment pragma.

This pragma has the following syntax:

#pragma member_alignment
#pragma member_alignment save
#pragma member_alignment restore
#pragma nomember_alignment [base_alignment]

When #pragma member_alignment is used, the compiler aligns structure members on the next boundary appropriate to the type of the member, rather than on the next byte. For example, a long variable is aligned on the next longword boundary; a short variable is aligned on the next word boundary. Consider the following example:


#pragma nomember_alignment 
struct x { 
           char c; 
           int b; 
           }; 
#pragma member_alignment 
 
struct y { 
          char c;       /*3 bytes of filler follow c */ 
          int b; 
          }; 
 
main () 
 
{ 
        printf( "The sizeof y is: %d\n", sizeof (struct y) ); 
        printf( "The sizeof x is: %d\n", sizeof (struct x) ); 
} 
When this example is executed, it shows the difference between #pragma member_alignment and #pragma nomember_alignment .

The optional base_alignment parameter can be used to specify the base-alignment of the structure. Use one of the following keywords for the base_alignment:

byte (1 byte)
word (2 bytes)
longword (4 bytes)
quadword (8 bytes)
octaword (16 bytes)

The #pragma member_alignment save and #pragma member_alignment restore directives can be used to save the current state of the member_alignment and to restore the previous state, respectively. This feature is necessary for writing header files that require member_alignment or nomember_alignment , or that require inclusion in a member_alignment that is already set.

2.1.1.9 #pragma message Directive

The #pragma message preprocessor directive controls the kinds of individual diagnostic messages or groups of messages that the compiler issues.

Default severities used by the compiler can be changed only if they are informationals, warnings or discretionary errors. Attempts to change more severe severities are ignored. If a message severity has not been altered by the command line and is not currently being controlled by a pragma, the compiler checks to see whether the message severity should be changed because of the "quiet" state. If not, the message is issued using the default severity.

Error message severities start out with command line severities applied to default compiler severities. Pragma message severities are then applied. In general, pragma severities override command line severities, which override default severities. The single exception this is that command line options can always be used to downgrade messages. However, command line options cannnot be used to raise the severity of messages currently controlled by pragmas.

The #pragma message directive has the following syntax:

#pragma message disable (message-list)
#pragma message enable (message-list)
#pragma message restore
#pragma message save
#pragma message error
#pragma message informational
#pragma message warning

disable

Suppresses the compiler-issued messages specified in the message-list argument. The message-list argument can be any one of the following:
  • A single message identifier
  • The keyword for a single message group as follows:
    all ---All messages issued by the compiler
    check ---All messages about potentially poor coding practices
    portable ---All messages about portability

  • A single message identifier enclosed in parentheses
  • A single message group name enclosed in parentheses
  • A comma-separated list of message identifiers or group names (freely mixed) enclosed in parentheses

The message identifier is the name following the message text when the -verbose option is set. A D: preceding the message identifier indicates that the message is discretionary; that is, you can change its severity or disable it. For example, consider the following message:


Non-void function "name" does not contain a return statement. (D:missingreturn). 

The message identifier is missingreturn . To prevent the compiler from issuing this message, use the following directive:


#pragma message disable missingreturn 

enable

Enables the compiler to issue the messages specified in the message-list argument.

restore

Restores the saved state of enabling or disabling compiler messages.

save

Saves the current state of enabling or disabling compiler messages.

The save and restore options are useful primarily within header files. See Section 2.1.1.3.

2.1.1.10 #pragma module Directive

When you compile source files to create an object file, the compiler assigns the first of the file names specified in the compilation unit to the name of the object file. The compiler adds the .o file extension to the object file. Internally, the operating system and the debugger recognize the object module by the file extension.

To change the system-recognized module name and version number, use the #pragma module directive.

You can find the module name and the module version number listed in the compiler listing file and the linker load map.

You can use the #pragma module directive when compiling in any mode.

The #pragma module directive has the following syntax:
#pragma module identifier identifier
#pragma module identifier string

The first parameter must be an identifier and specifies the module name. This is primarily used by the linker on OpenVMS systems, but is also used in the heading of the listing file. The second parameter specifies the optional identification. See Section 2.1.1.5 for more information about this.

2.1.1.11 #pragma once Directive

The #pragma once preprocessor directive specifies that the header file is evaluated only once.

The #pragma once directive has the following format:

#pragma once

2.1.1.12 #pragma pack Directive

The #pragma pack preprocessor directive specifies packing alignment for structure and union members. Whereas the packing alignment of structures and unions is set for an entire translation unit by the -ZpN (N = 1, 2, or 4) and the -nomember_align option, the packing alignment is set at the data-declaration level by the pack pragma. The pragma takes effect at the first structure or union declaration after the pragma is seen; the pragma has no effect on definitions.

The #pragma pack directive has the following format:

#pragma pack [(n)]

When you use #pragma pack(n), where n is 1, 2, 4, 8, or 16, each structure member after the first is stored on the smaller member type or n-byte boundaries. If you use #pragma pack without an argument, structure members are packed to the value specified by -Zp . The default -Zp packing size is -Zp8 .

The compiler also supports the following enhanced syntax:

#pragma pack( [ [ { push | pop}, ] [ identifier, ] ] [ n ] )

This syntax allows you to combine program components into a single translation unit if the various components use pack pragmas to specify different packing alignments.

Each occurrence of a pack pragma with a push argument stores the current packing alignment on an internal compiler stack. The pragma's argument list is read from left to right. If you use push, the current packing value is stored. If you provide a value for n, that value becomes the new packing value. If you specify an identifier, a name of your choosing, the identifier is associated with the new packing value.

Each occurrence of a pack pragma with a pop argument retrieves the value at the top of an internal compiler stack and makes that value the new packing alignment. If you use pop and the internal compiler stack is empty, the alignment value is that set from the command-line and a warning is issued. If you use pop and specify a value for n, that value becomes the new packing value. If you use pop and specify an identifier, all values stored on the stack are removed from the stack until a matching identifier is found. The packing value associated with the identifier is also removed from the stack and the packing value that existed just before the identifier was pushed becomes the new packing value. If no matching identifier is found, the packing value set from the command line is used and a level-one warning is issued. The default packing alignment is 8.

The pack pragma allows you to write header files that ensure that packing values are the same before and after the header file is encountered:


/* File name: include1.h 
*/ 
#pragma pack( push, enter_include1 ) 
/* Your include-file code ... */ 
#pragma pack( pop, enter_include1 ) 
/* End of include1.h */ 

In the previous example, the current pack value is associated with the identifier enter_include1 and pushed, remembered, on entry to the header file. The pack pragma at the end of the header file removes all intervening pack values that may have occurred in the header file and removes the pack value associated with enter_include1 . The header file thus ensures that the pack value is the same before and after the header file.

The new functionality also allows you to use code, such as header files, that uses pack pragmas to set packing alignments that differ from the packing value set in your code:


#pragma pack( push, before_include1 ) 
#include "include1.h" 
#pragma pack( pop, before_include1 ) 
In the previous example, your code is protected from any changes to the packing value that might occur in include.h .

2.1.1.13 #pragma pointer_size Directive [Tru64]

The #pragma pointer_size preprocessor directive controls pointer size allocation for the following:

  • References
  • Pointers to member declarations
  • Function declarations
  • Array declarations

For this pragma to have any effect, you must specify -xtaso , -xtaso_short , -vptr_size , or -vptr_size_short on the cxx command.

This pragma has the following syntax:

#pragma pointer_size {long|64}
#pragma pointer_size {short|32}
#pragma pointer_size restore
#pragma pointer_size save

long, or 64

Sets as 64 bits all pointer sizes in declarations that follow this directive, until the compiler encounters another #pragma pointer_size directive.

short, or 32

Sets as 32 bits all pointer sizes in declarations that follow this directive, until the compiler encounters another #pragma pointer_size directive.

restore

Restores the saved pointer size from the pointer size stack.

save

Saves the current pointer size on a pointer size stack.

The save and restore option are particularly useful for specifying mixed pointer support and for protecting header files that interface to older objects. Objects compiled with multiple pointer size pragmas will not be compatible with old objects, and the compiler cannot discern that incompatible objects are being mixed.

Use of short pointers is restricted to the HP C++ and the C compilers resident on HP Tru64 UNIX systems. Programs should not attempt to pass short pointers from C++ routines to routines written in any other language except the C programming language.

HP C++ might require explicit conversion of short pointers to long pointers in applications that use short pointers. You should first port those applications in which you are considering using short pointers, and then analyze them to determine if short pointers would be beneficial.

A difference in the size of a pointer in a function declaration is not sufficient to overload a function.

The compiler issues an error-level diagnostic if:

  • Two functions defined differ in argument types only with respect to pointer sizes.
  • Two functions differ in return type only with respect to pointer size.
  • The compiler encounters a member function that tries to overload another that differs only with respect to the pointer size of the this parameter.

2.1.1.14 #pragma required_pointer_size Directive [Tru64]

The #pragma required_pointer_size preprocessor directive controls pointer size allocation in the same way as #pragma pointer_size but without interaction with command-line options. This pragma is always enabled, whether or not you specify any pointer size options on the command line. The same syntax, precautions, and restrictions pertain as with #pragma pointer_size . Neither the pragma name nor its arguments are subject to macro expansion.

2.1.1.15 #pragma required_vptr_size Directive [Tru64]

The #pragma required_vptr_size preprocessor directive controls pointer size allocation in the same way as #pragma required_pointer_size , but it applies to virtual function pointers and virtual bases in a C++ class object. This pragma has the following syntax:

#pragma required_vptr_size {long|64}
#pragma required_vptr_size {short|32}
#pragma required_vptr_size restore
#pragma required_vptr_size save

The parameters have the same meaning as with #pragma required_pointer_size (see Section 2.1.1.13).

The #pragma required_vptr_size directive takes effect at the opening brace of a class declaration. This pragma is always enabled, whether or not you specify any pointer size options on the command line.

2.1.1.16 #pragma [no]standard Directive

Use the #pragma nostandard and #pragma standard preprocessor directives to disable all but the most significant messages from header file processing.

This pragma has the following syntax:

#pragma [no]standard

Use #pragma nostandard to mark the start of reduced message activity.

Use #pragma standard to mark the resumption of normal message activity.

2.1.1.17 #pragma weak Directive

Use the #pragma weak preprocessor directive to create weak symbols. Weak symbols are symbols that can be overridden by strong symbols. Symbols are strong by default.

This pragma has the following syntax:

#pragma weak weak_name
#pragma weak weak_name = strong_name
#pragma weak ( weak_name, strong_name )

The first form of the pragma makes the specified name a weak symbol. The other two make a weak symbol that refers to the same symbol as the other name.

2.1.2 Protecting System Header Files

It is important to protect system header files from generating diagnostics and from user-specified changes to member alignment and pointer sizes.

To provide the necessary protection you can do one of the following:

  • Use a header file protection option provided by the compiler.
  • Modify each header file.

2.1.2.1 Using the Compiler's Header File Protection Option

With this option, you can place special header files in a directory. the compiler processes these special header files before and after each file included with the #include directive from this directory. These special header files are named:
__DECC_include_prologue.h
__DECC_include_epilogue.h

The compiler checks for files with these special names when processing #include directives. If the special prologue file exists in the same directory as a file with the #include directive, the contents of the prologue file are processed just before the file included with the #include directive. Similarly, if the epilogue file exists in the same directory as the file included with the #include directive, it is processed just after that file.

For convenience, you can protect header files using the script supplied in the following directory:


/usr/lib/cmplrs/cxx/protect_system_headers.sh 

This script creates, in all directories in a directory tree that contain header files, symbolic links to HP-supplied header prologue and epilogue files.

The default directory tree root assumed by the script is /usr/include , but you can specify other roots.

2.1.2.2 Modifying Each Header File

If you choose to modify each header file that needs protection, you can use the #pragma environment directive, as in the following example:


#pragma __environment save              // Save pointer size 
#pragma __environment header_defaults   // set to system defaults 
 
// existing header file 
 
#pragma__environment restore            // Restore pointer size 

See Section 2.1.1.3 for more information about using this pragma.

2.1.3 Predefined Macro Names

The following tables list predefined C++ macros used by HP C++. These names are useful in preprocessor #ifdef and #if defined directives to isolate code intended for one of the particular cases. The names can be used anywhere you use other defined names, including within macros.

For information on using predefined macros in header files in the common language environment, see Section 3.1.

Table 2-1 Standard Macro Names
__cplusplus Defined by the C++ compiler when compiling source code.
__LINE__ The current line number (a decimal integer).
__FILE__ The current file name (a character string).
__DATE__ The source file's compilation date (a character string literal of the form "Mmm dd yyyy", where the month names are the same as those generated by the asctime function, and the first character of "dd" is a space character if the value is less than 10.)
__STDC__ [Linux] Compiler supports ANSI C language constructs.
__TIME__ The source file's compilation time (a character string literal of the form "hh:mm:ss"), as in the time generated by the asctime function.

Table 2-2 System Identification Macro Names
Macro Name Description
__alpha 1, __alpha__, __ALPHA System has an Alpha processor.
__arch64__ System with 64-bit architecture.
__digital__ System identification name on Tru64 UNIX.
__linux, __linux__ 1 System is a Linux Alpha system.
linux [Linux] System is a Linux Alpha system. Provided for GNU compatibility; use of this macro is not recommended. However, some open-source libraries use it.
__osf__ System is a Tru64 UNIX system.
__unix, __unix__ System is a UNIX system.
unix [Linux] System is a UNIX system. Provided for GNU compatibility; use of this macro is not recommended.


1Use this form for portability.

Table 2-3 Other Predefined Macro Names
Macro Name Description
_BOOL_EXISTS Indicates that bool is a typedef or keyword
__BOOL_IS_A_RESERVED_WORD Indicates that bool is a keyword
__DECCXX Compiler is HP C++.
__DECCXX_VER Compiler version identifier in string form.
__IEEE_FLOAT The compiler supports IEEE floating point.
__INITIAL_POINTER_SIZE [Tru64] A decimal constant containing the initial pointer size allocation, as specified by the command line. Legal values on Tru64 UNIX are: 0 (no pointer size option set), 32 (short or 32-bit pointers allocated), and 64 (long or 64-bit pointers allocated). The only legal value on Linux Alpha is 0.
__INTRINSICS HP C++ recognizes intrinsic functions.
__LANGUAGE_C__, __LANGUAGE_C,
LANGUAGE_C
[Linux] Compiler translates C language constructs and keywords. Provided for GNU compatibility; use of these macros is not recommended.
_LONGLONG Compiler supports "long long" type declarations.
__PRAGMA_ENVIRONMENT The compiler supports pragma environment.
__STDC_VERSION__ Version number of ANSI C support, in string form; not defined by the C++ compiler.
_SYSTYPE_BSD Compiler supports BSD type system headers.
_WCHAR_T 1, __WCHAR_T 2 wchar_t is a typedef or a keyword.
__X_FLOAT System has default long double size of 128 bits.


1Use this form for portability.
2This form is used on OpenVMS systems.

Predefined Language Dialect Macro Names

Table 2-4 shows the macro names defined by specifying the listed command-line options.

Table 2-4 Language Dialect Macro Names
Command-line Option Macro Name
-std arm __STD_ARM
-std ansi __STD_ANSI
-std cfront, -cfront As of C++ Version 7.1, the -std cfront and -cfront options are no longer supported.
-std gnu __STD_GNU
-std ms, -ms __MS, __STD_MS
-std strict_ansi __STD_STRICT_ANSI, __PURE_CNAME
-std strict_ansi_errors __STD_STRICT_ANSI_ERRORS,
__PURE_CNAME

Predefined Implementation Compatibility Macro Names

Table 2-5 shows the macro names defined by specifying the listed command-line options.

Table 2-5 Implementation Compatibility Macro Names
Command-line Option Macro Name
-alternative_tokens __ALTERNATIVE_TOKENS
-fprm __FLT_ROUNDS
-global_array_new __GLOBAL_ARRAY_NEW
-ieee __IEEE_FP
-implicit_include __IMPLICIT_INCLUDE_ENABLED
-long_double_size __X_FLOAT
-model ansi __MODEL_ANSI
-model arm __MODEL_ARM
-pch , -create_pch , -use_pch __PCH_ENABLED
-pthread _REENTRANT
-pure_cname __PURE_CNAME
-rtti __RTTI
-stdnew __STDNEW
-threads _REENTRANT, _PTHREAD_USED_D4
-using_std __IMPLICIT_USING_STD

Predefined __DECCXX_VER Version Number Macro

These macros provide an integer encoding of the compiler version-identifier string that is suitable for use in a preprocessor #if expression, such that a larger number corresponds to a more recent version.

The format of the compiler version-identifier string is:


TMM.mm-eee

Where:

  • T is the version type (letter).
  • MM is the major version number.
  • mm is the update (minor version number).
  • eee is the edit suffix number.

The format of the integer encoding for __DECCXX_VER is:


vvuuteeee

Where:

  • vv is the major version number.
  • uu is the update (minor version number).
  • t is the numerical encoding of the alphabetic version type from the version-identifier string.
    Table 2-6 lists the possible version types and their encodings
  • eeee is the edit suffix number.

Table 2-6 __DECC_VER Version-Type Encodings
Type Numerical Encoding Description
T 6 Field-test version
S 8 Customer special
V 9 Officially supported version

The following describes how the __DECCXX_VER integer value is calculated from the compiler version-identifier string:

  1. The major version is multiplied by 10,000,000 (ten million).
  2. The minor version (the digits between the period (.) and any edit suffix) is multiplied by 100,000 (one hundred thousand) and added to the suffix value (the suffix value has a range of 0 to 999).
  3. If the character immediately preceding the first digit of the major version number is one of those listed in Table 2-6, its numerical encoding is multiplied by 10000.
  4. The preceding values are added together.

The following examples show how different compiler version-identifier strings map to __DECCXX_VER encodings:


ident           __DECCXX_VER 
string          vvuuteeee
 
T5.2-003   -->   50,260,003 
V6.0-001   -->   60,090,001 

2.1.4 Translation Limits

The only translation limits imposed in the compiler are as follows:
Limit Meaning
32,767 Characters in an internal identifier or macro name.
32,767 Characters in a logical or physical source line.
32,767 Bytes in the representation of a string literal. This limit does not apply to string literals formed by concatenation.
32,767 Significant characters in an external identifier. A warning is issued if such an identifier is truncated. Linking further limits external identifiers to 1022 characters.

2.1.5 Numerical Limits

The numerical limits, as defined in the header files <limits.h> and <float.h> , are as follows:

  • The number of bits in a character of the execution character set is 8 bits.
  • The representation and set of values for type char and for type signed char are the same. You can change this equivalence from signed char to unsigned char using the -unsigned option (see the cxx(1) reference page).
  • The representation and set of values for the short type is 16 bits.
  • The representation and set of values for the types int , unsigned int , and float are 32 bits.
  • The representation and set of values for type double are 64 bits.
  • The representation and set of values for type long double is 128 bits on HP Tru64 UNIX Version 5.0 and later. You can change this equivalence using the -long_double_size 64 option (see the cxx(1) reference page).

Numerical limits not described in this list are defined in the C++ International Standard.

2.1.6 Argument-Passing and Return Mechanisms

Even if an argument is declared pass by value, the compiler passes arrays, functions, and class objects that have non-trivial copy constructors or destructors by reference. In the first two cases, this behavior is defined by the language standard.

Class objects have non-trivial copy constructors if they have user-declared copy constructors, or if they have virtual bases, virtual functions, or non-static data members that require virtual table initialization.

In the case of a class with a non-trivial copy constructor or a destructor, the compiler calls a copy constructor to copy the object to a temporary location, and then it passes the address of that location to the called function. All other objects are passed by value.

If the return value of a function is a class that has a non-trivial copy constructor or destructor or is greater than 64 bits, storage is allocated by the caller, and the address of this storage is passed in the first parameter to the called function. The called function uses the storage provided to construct the return value.

2.2 Implementation Extensions and Features

This section describes the extensions and implementation-specific features of HP C++ for Tru64 UNIX and Linux Alpha systems.

2.2.1 Identifiers

In HP C++, the dollar sign ($) is a valid character in an identifier.

For each external function with C++ linkage, the compiler decorates the function name with a representation of the function's type.

2.2.1.1 Character Limit for Long Names

If an external name has more than 1022 characters, the compiler truncates the name to 1022 characters. The compiler keeps the first 1015 characters intact, reduces (hashes) the remaining characters to a string of 7 characters, and appends the 7 hashed characters to the first 1015.

2.2.2 Order of Static Object Initialization

The order of static objects is defined by the implementation. Relying on this order is not good programming practice. For HP C++, static objects are initialized in declaration order within a compilation unit. On Tru64 UNIX the order across compilation units is the order on the link. On Linux Alpha, the order across compilation units is reversed. The order is a function of the linker, not the compiler, and is the same as g++ on Linux.

2.2.3 Integral Conversions

When demoting an integer to a signed integer, if the value is too large to be represented, the result is truncated and the high-order bits are discarded.

Conversions between signed and unsigned integers of the same size involve no representation change.

2.2.4 Floating-Point Conversions

When converting an integer to a floating-point number that cannot exactly represent the original value, the compiler rounds off the result of the conversion to the nearest value that can be represented exactly.

When the result of converting a floating-point number to an integer or other floating-point number at compile time cannot be represented, the compiler issues a diagnostic message.

When converting an integral number or a double floating-point number to a floating-point number that cannot exactly represent the original value, the compiler rounds off the result to the nearest value of type float .

When demoting a double value to float , if the converted value is within range but cannot exactly represent the original value, the compiler rounds off the result to the nearest representable float value.

The compiler performs similar rounding for demotions from long double to double or float .

2.2.5 Explicit Type Conversion

In HP C++, the expression T() (where T is a simple type specifier) creates an rvalue of the specified type, whose value is determined by default initialization. According to The C++ Programming Language, 3rd Edition, the behavior is undefined if the type is not a class with a constructor, but the ANSI working draft removes this restriction. With this change you can now write:


    int i=int(); // i must be initialized to 0 

2.2.6 The sizeof Operator

The type of the sizeof operator is size_t . In the header file <stddef> , this type is defined as unsigned long , which is the type of the integer that holds the maximum size of an object.

2.2.7 Explicit Type Conversion

A pointer takes up the same amount of memory storage as objects of type long (or the unsigned equivalent). Therefore, a pointer can convert to any of these types and back again without changing its value. No scaling occurs and the representation of the value is unchanged.

Conversions to and from a shorter integer and a pointer are similar to conversions to and from a shorter integer and unsigned long . If the shorter integer type was signed, conversion fills the high-order bits of the pointer with copies of the sign bit.

2.2.8 Multiplicative Operators

The semantics of the division (/) and remainder (%) operator are as follows:

  • If either operand of the division operator is negative, the compiler truncates the result toward 0 (that is, the smallest integer larger than the algebraic quotient).
  • If either operand of the remainder operator is negative, the result takes the same sign as that of the first operand.

In the following cases of undefined behavior detected at compile time, the compiler issues a warning:

Integer overflow
Division by 0
Remainder by 0

2.2.9 Additive Operators

You can subtract pointers to members of the same array or a pointer that points just past the end of an array. The result is the number of elements between the two array members, and is of type ptrdiff_t . In the header file <stddef.h> , the compiler defines this type as long .

2.2.10 Shift Operators

The expression E1 >> E2 shifts E1 to the right E2 positions. If E1 has a signed type, the compiler fills the vacated high-order bits of the shifted value E1 with a copy of E1 's sign bit (arithmetic shift).

2.2.11 Equality Operators

When comparing two pointers to members, the compiler guarantees equality if either of the following conditions hold:

  • Both pointers are NULL.
  • The same address expression (&) created both pointers.

When comparing two pointers to members, the compiler guarantees inequality if either of the following conditions hold:

  • Only one pointer is NULL.
  • Each pointer produces a different member if applied to the same object.

When created by different address expressions, two pointers to members may compare as equal if they produce the same member when applied to the same object; otherwise, they are unequal.

2.2.12 volatile Type Specifier

For variables that are modifiable in ways unknown to the compiler, use the volatile type specifier. Declaring an object to be volatile means that every reference to the object in the source code results in a reference to memory. Volatile quantities are also referenced atomically in the hardware; that is, they are fetched and stored with single assembler instructions. Attempts to make a volatile quantity be declared as not aligned incurs a compiler error.

Any object whose type includes the volatile type qualifier indicates that the object should not be subject to compiler optimizations altering references to, or modifications of, the object.

Optimizations that are defeated by using the volatile specifier can be categorized as follows:

  • Optimizations that alter an object's duration; for example, cases where references to the object are shifted or moved to another part of the program.
  • Optimizations that alter an object's locality; for example, cases where a variable serving as a loop counter is stored in a register to save the cost of doing a memory reference.
  • Optimizations that alter an object's existence; for example, loop induction to actually eliminate a variable reference.

An object without the volatile specifier does not compel the compiler to perform these optimizations; it indicates that the compiler has the freedom to apply the optimizations depending on program context and compiler optimization level.

The volatile specifier forces the compiler to allocate memory for the volatile object, and to always access the object from memory. This qualifier is often used to declare that an object can be accessed in some way not under the compiler's control. Therefore, an object qualified by the volatile keyword can be modified or accessed in ways by other processes or hardware, and is especially vulnerable to side effects.

The following rules apply to the use of the volatile specifier:

  • The volatile specifier can be used to qualify any data type, including a single member of a structure or union.
  • Redundant use of the volatile keyword elicits a warning message. For example:


    volatile volatile int x; 
    

  • When volatile is used with an aggregate type declaration, all members of the aggregate type are qualified with volatile. When volatile is used to qualify a member of an aggregate type, only that member is qualified. For example:


    volatile struct employee { 
        char *name; 
        int   birthdate; /* name, birthdate, job_code, and salary are */ 
        int   job_code;  /* treated as though declared with volatile. */ 
        float salary; 
        } a,b;          /*  All members of a and b are volatile-qualified  */ 
     
    struct employee2 { 
        char *name; 
        volatile int birthdate;  /*  Only this member is qualified    */ 
        int job_code; 
        float salary; 
        } c, d; 
    

    If the tag employee is used to specify another structure later in the program, the volatile specifier does not apply to the new structure's members unless explicitly specified.
    The const specifier can be used with the volatile specifier. This is useful, for example, in a declaration of a data object that is immutable by the source process but can be changed by other processes, or as a model of a memory-mapped input port such as a real-time clock. The address of a non-volatile object can be assigned to a pointer that points to a volatile object. For example:


    const int *intptr; 
    volatile int x; 
    intptr = &x; 
    

    Likewise, the address of a volatile object can be assigned to a pointer that points to a non-volatile object.

2.2.13 __unaligned Type Specifier

Use this data-type specifier in pointer definitions to indicate to the compiler that the data pointed to is not properly aligned on a correct address. (To be properly aligned, the address of an object must be a multiple of the size of the type. For example, two-byte objects must be aligned on even addresses.)

When data is accessed through a pointer declared __unaligned , the compiler generates the additional code necessary to copy or store the data without causing alignment errors. It is best to avoid use of misaligned data altogether, but in some cases the usage may be justified by the need to access packed structures, or by other considerations.

Here is an example of a typical use of __unaligned :


typedef enum {int_kind, float_kind, double_kind} kind; 
void foo(void *ptr, kind k) { 
    switch (k) { 
    case int_kind: 
        printf("%d", *(__unaligned int *)ptr); 
        break; 
    case float_kind: 
        printf("%f", *(__unaligned float *)ptr); 
        break; 
    case double_kind: 
        printf("%f", *(__unaligned double *)ptr); 
        break; 
    } 
} 

2.2.14 Linkage Specifications

Specifying linkage other than "C++" or "C" generates a compile-time error.

In object files, the compiler decorates with type information the names of functions with C++ linkage. This permits overloading and provides rudimentary type checking across compilation units. The type-encoding algorithm used is similar to that given in §7.2.1c of The Annotated C++ Reference Manual.

2.2.15 Temporary Objects

The compiler creates temporary objects for class objects with constructors:

  • An object is returned from a function.
  • An object is passed as an argument.
  • An object is created using the constructor notation.
  • A user-defined conversion is implicitly used.

Variations in the compiler generation of such temporary objects can adversely affect their reliability in user programs. The compiler avoids introducing a temporary object whenever it discovers that the temporary object is not needed for accurate compilation. Therefore, you should modify or write your programs so as not to depend on side effects in the constructors or destructors of temporary objects.

2.2.15.1 Nonconstant Reference Initialization with a Temporary Object

If your program tries to initialize a nonconstant reference with a temporary object, the compiler generates a warning. For example:


struct A { 
  A(int); 
}; 
void f(A& ar); 
 
void g() { 
  f(5);  // warning!! 
} 

2.2.16 Exception Handling

The compiler optimizes the implementation of exception handling for normal execution, as follows:

  • Applications that do not have exception handlers incur no overhead.
  • Applications with handlers that run without causing exceptions incur only slight overhead:
    • The size of an executable image increases slightly because of the tables that describe the handlers.
    • Code optimization is less aggressive in the presence of handlers.
  • As much as possible, overhead for exceptions is incurred when throwing an exception. Entering or leaving a try block incurs no overhead.

In HP C++, a procedure with handlers has no intrinsic overhead. For example, procedures with handlers do not have frame pointers or additional register usage.

Some procedures without explicit handlers may have implicit handlers. The compiler creates a handler for each automatic object that has a destructor. The compiler also creates handlers for constructors that initialize subobjects that have destructors. In such a constructor, the compiler creates a handler for each member with a destructor, and a handler for each base class with a destructor.

The -nocleanup option suppresses generation of such implicit handlers, which results in an executable file that is slightly smaller. You should use this option for programs that do not use exception handling or that do not require destruction of automatic objects during exception processing.

Exception specifications in function prototypes that conflict with function definitions are illegal. Beware of such conflicting exception specifications, particularly if you have exception specifications in prototypes that are in header files accessible to other programs.

2.2.17 File Inclusion

The #include directive inserts external text into the source stream delivered to the compiler. Programmers often use this directive to include global definitions for use with HP C++ functions and macros in the program stream.

The #include directive has the following search path semantics:

  • Quoted path names:
    1. Quoted path names are first searched for in the directory that contains the file with the #include directive.
    2. If the file is not found, the search continues to the list of directories specified on the command line with the -I directoryname compiler option, in order.
    3. If the file is still not found, the compiler searches the /usr/include/cxx , /usr/include/cxx_cname , and /usr/include directories, then in the /usr/include/cxx_cname directory.
  • Angle-bracketed files are searched for in the list of directories specified on the command line, then in the /usr/include/cxx , /usr/include/cxx_cname , and finally in the /usr/include directory.
  • By default, the compiler searches for include files in the standard directories /usr/include/cxx , /usr/include/cxx_cname , and in the /usr/include directory in the order described above. If you want to override this behavior and force the compiler to search only in directories specified on the command line, use the -I open without any arguments. (See the description of the -I option in the cxx.1 reference page.)

2.2.18 Inheritance and Header Files

Inheritance means that one object or file derives some of its contents by virtual copying from another object or file. In the case of C++ header files, for example, one header file includes another and then replaces or adds something.

If the inheriting header file and the base header file have different names, inheritance is straightforward: simply write #include "base" in the inheriting file.

However, if it is necessary to give the inheriting file the same name as the base file, inheritance is less straightforward.

For example, suppose an application program uses the system header file sys/signal.h , but the version of /usr/include/sys/signal.h on a particular system does not do what the program expects. You could define a local version, perhaps with the name /usr/local/include/sys/signal.h , to override or add to the one supplied by the system. Using the -I option for compilation, you could then write a file sys/signal.h that does what the application program expects. But if you try to include the standard sys/signal.h in your version using #include <sys/signal.h> , the result is an infinite recursion and a fatal compilation error.

Specifying #include </usr/include/sys/signal.h> would include the correct file, but that technique makes maintenance difficult because it assumes that the location of the system header files will never change.

You should therefore use the #include_next directive, which means "Include the next file with this name." This directive works like #include but starts searching the list of header file directories after the directory in which the current file was found.

Suppose you specify -I /usr/local/include , and the list of directories to search also includes /usr/include . Then suppose that both directories contain a file named sys/signal.h . Specifying #include <sys/signal.h> finds your version of the file in the /usr/local/include directory. If that file contains #include_next <sys/signal.h> , the search starts after that directory and finds the correct system standard file in /usr/include .

2.2.19 Nested Enums and Overloading

The C++ language allows programmers to give distinct functions the same name, and uses either overloading or class scope to differentiate the functions:


void f(int); 
void f(int *); 
class C {void f(int);}; 
class D {void f(int);}; 

All compilers, including HP C++, use name mangling to assign unique names to these functions. These unique mangled names allow the linker to tell the overloaded functions apart.

The compiler forms a mangled name by appending an encoding of the parameter types of the function to the function's name. If the function is a member function, the compiler qualifies the function name by the names of the classes within which it is nested.

For example, for the function declarations at the beginning of this section, the compiler generates the mangled names f__Xi , f__XPi , f__1CXi , and f__1DXi respectively. In these names, i means a parameter type was int , P means "pointer to", 1C means nested within class C , and 1D means nested within class D .

There is a flaw in the name mangling scheme used by the compiler that can cause problems in uncommon cases. The compiler fails to note in the encoding of an enum type in a mangled name whether the enum type was nested within a class. This can cause distinct overloaded functions to be assigned the same mangled name:


#include <stdlib.h> 
struct C1 {enum E {red, blue};}; 
struct C2 {enum E {red, blue};}; 
 
extern "C" int printf(const char *, ...); 
void f(C1::E x) {printf("f(C1::E)\n");} 
void f(C2::E x) {printf("f(C2::E)\n");} 
 
int main() 
{ 
    f(C1::red); 
    f(C2::red); 
    return EXIT_SUCCESS; 
} 

In the previous example, the two overloaded functions named f differ only in that one takes an argument of enum type C1::E and the other takes an argument of enum type C2::E . Because the compiler fails to include the names of the classes containing the enum type in the mangled name, both functions have mangled names that indicate the argument type is just E . This causes both functions to receive the same mangled name.

In some cases, the compiler detects this problem at compile-time and issues a message that both functions have the same type-safe linkage. In other cases, the compiler issues no message, but the linker complains about duplicate symbol definitions.

If you encounter such problems, you can recompile using the
-distinguish_nested_enums command line switch. This causes the compiler to include the name of class or classes that an enum is nested within when forming a mangled name. This eliminates cases where different functions receive the same mangled name.

Because the -distinguish_nested_enums command-line switch changes the external symbols the compiler produces, you can get undefined symbol messages from the linker if some modules are compiled with -distinguish_nested_enums and some are compiled without it. Because of this, -distinguish_nested_enums might make it difficult to link against old object files or libraries of code.

If you compile your code with -distinguish_nested_enums and try to link against a library that was compiled without the -distinguish_nested_enums command line switch, you will receive an undefined symbol message from the linker if you attempt to call a function from the library that takes an argument of a nested enum type. The mangled name of the function in the library will be different from the mangled name your code is using to call the function.

2.2.20 Guiding Declarations

A guiding declaration is a function declaration that matches a function template, does not introduce a function definition (implies an instantiation of the template body and not a explicit specialization), and is subject to different argument matching rules than those that apply to the template itself---therefore affecting overload resolution. Consider the following example:


template <class T> void f(T) { 
  printf("In template f\n"); 
} 
 
void f(int); 
 
int main() { 
  f(0);      // invokes non-template f 
  f<>(0.0);  // invokes template f 
  return 0; 
} 
 
void f(int) { 
  printf("In non-template f\n"); 
} 

Because there is no concept of guiding declaration in the current version of the C++ International Standard, the function f in the example is not regarded as an instance of function template f . Furthermore, there are two functions named f that take an int parameter. A call of f(0) would invoke the former, while a call of f<>(0) would be required to invoke the latter.

2.3 Run-time Type Identification

The compiler emits type information for Run-Time Type Identification (RTTI) in the object module with the virtual function table, for classes that have virtual function tables.

You can specify the -[no]rtti option to enable or disable support for RTTI (runtime type identification) features: dynamic_cast and typeid . Disabling runtime type identification can also save space in your object file because static information to describe polymorphic C++ types is not generated. The default is to enable runtime type information features and generate static information in the object file.

Specifying -nortti does not disable exception handling.

The type information for the class may include references to the type information for each base class and information on how to convert to each. The typeinfo references are mangled in the form __T__<class> .

2.4 Implementation of Polymorphism During Object Construction

During object construction, the compiler allocates intermediate virtual function tables on the constructor's stack for each virtual base class. If the object currently under construction is a subobject of a more derived object, the compiler uses the local virtual function tables. Each table's Run-Time Type Identification contains the offset to convert successfully from the virtual base class to the object being constructed. This offset is known at runtime through the object's virtual base table and can be assigned into the corresponding local virtual function table's RTTI information. Virtual function calls that occur during construction can then correctly adjust the "this" pointer.

Compiler-generated thunks to this pointers ("interludes") take advantage of the RTTI's offset:


    void __INTER__1S_funcname_2Vn_type(Vn *this) 
    { 
        this = this + this.vptr[RTTI_INDEX].offset; 
        funcname(this); 
    } 

Again, this offset represents the proper delta to convert from a virtual base class to the more derived class that overrode the virtual function declared within the virtual base class. For example:


    struct V { 
        V() { f(); } 
        virtual void f() { g(); } 
        virtual void g() { printf("V::g, this = %x\n", this); } 
    }; 
 
    struct S : virtual V { 
        S() { f(); } 
        virtual void g() { printf("S::g, this = %x\n", this); } 
    }; 
 
    struct D : virtual S { 
        D() { f(); } 
        virtual void g() { printf("D::g, this = %x\n", this); } 
    }; 
 
 
    S's constructor would be rewritten as follows: 
 
    S() 
    { 
        if (most_derived) { 
            Assign btbls; 
            Call virtual base class constructors; 
        } 
        
        if (!most_derived) { 
                
                /* For each virtual base generate the following code */ 
                /* Vn represents a particular virtual base class */ 
 
                int *__local_vtbl__2Vn1S[Vn_vtbl_compile_time_known_size]; 
                        
                memmove(__local_vtbl__2Vn1S, __vtbl__2Vn1S, 
                Vn_vtbl_compile_time_known_size); 
 
                __local_vtbl__2Vn1S[RTTI_INDEX].offset = S.bptr[Vn]; 
        
                Vn.vptr = &__local_vtbl__2Vn1S;     
 
        } else { 
                Assign vtbls like we do today; 
        }       
    
        Call non-virtual direct base class constructors 
 
        /* body of S's constructor */ 
 
        if (!most_derived) { 
            Reassign vtbls like we do today; 
        } 
    }        

2.5 Message Control Options

The compiler supports the following message control options. The options apply only to discretionary, warning, and informational messages. The tag variable can be the keyword all , a tag obtained from -msg_display_tag , or a number obtained from -msg_display_number . The tag variable is preferred.

-msg_inform tag,...

Reduce message(s) severity to informational.

-msg_warn tag,...

Reduce message(s) severity to warning.

-msg_error tag,...

Increase message(s) severity to error

-msg_enable tag,...

Enable specific messages that would normally not be issued. You can also use this option to re-enable messages disabled with -msg_disable .

-msg_disable tag,...

Disable message. Can be used for any nonerror message.

-msg_quiet

Be more like Version 5.n error reporting. Fewer messages are issued using this option. This is the default in arm mode ( -std arm ). All other modes default to -nomsg_quiet .

You can use the msg_enable option with this option to enable specific messages normally disabled using -msg_quiet .

2.6 Message Information Options

The compiler supports the following message information options. Both are disabled by default.

Note

"D" (meaning discretionary) indicates that the severity of the message can be controlled from the command line. The message number can be used as the tag in the message control options described in Section 2.5. If "D" is not displayed with the message number, any attempt to control the message is ignored.

-msg_display_tag

A more descriptive tag is issued at the end of each message issued. "D" indicates the severity of the message can be controlled from the command line. The tag displayed can be used as the tag in the message control options described in Section 2.5.

Example:


cxx -msg_display_tag t.cxx 
cxx: ... is nonstandard ("int" assumed) (D:nonstd_implicit_int) 
cxx -msg_disable nonstd_implicit_int  t.cxx 

Note that you can change the severity of a diagnostic message if the message is discretionary. For example, -msg_inform nonstd_implicit_int changes the severity to an informational. These options interact with -w0 , -w1 , and -w2 .

-msg_display_number

The error number is displayed at the beginning of each message issued.

Example:


cxx -msg_display_number t.cxx 
cxx: Warning: t.cxx, line 1: #117-D non-void function "f" ... 
cxx -msg_disable 117 t.cxx 


Previous Next Contents Index

Privacy statement Using this site means you accept its terms
© 2005 Hewlett-Packard Development Company, L.P.