HP C/HP-UX Online Help

Return to the Main HP C Online Help page



Programming for Portability

Porting to the 64-bit Architecture
Guidelines for Portability
Practices to Avoid
General Portability Considerations
Porting to ANSI Mode HP C
Using Name Spaces in HP C and ANSI C
Silent Changes for ANSI C
Porting between HP C and Domain/C
Porting between HP C and VMS C

Portable C programs are clear, reliable, and easily maintainable and can be easily transported from one machine to another. With few modifications, C programs written with portability in mind can be recompiled and run on different computers. For specific information on system dependencies, refer to the HP C/HP-UX Reference Manual.

The ANSI standard specifies which aspects of C are required to work the same on conforming implementations, and which can work differently. Since many ANSI-conforming compilers are available on a wide variety of platforms, it is easy to develop portable programs. HP C, when invoked in ANSI mode and used with the preprocessor (cpp), headers, libraries, and linker, conforms fully with the standard.

This section discusses some guidelines for making your C programs more portable. Emphasis is placed on HP C specific portability issues, especially as they relate to porting from pre-ANSI mode HP C (Kernighan and Ritchie plus BSD extensions) to ANSI mode HP C.

Porting to the 64-bit Architecture

Refer to the HP-UX 64-bit Porting and Transition Guide for details regarding porting from the 32-bit data model (ILP32) to the 64-bit data model (LP64). The HP-UX 64-bit Porting and Transition Guide is available on the 11.0 CD-ROM and on the World Wide Web at the following URL:

http://docs.hp.com/hpux/development/

Guidelines for Portability

This section lists some things you can do to make your HP C programs more portable, including the following: Use the ANSI C compiler option whenever possible when writing new programs. HP C conforms to the standard when it is invoked with the -Aa option. The -w and +e options should not be used with the -Aa option, as these options will suppress warning messages and allow non-conforming extensions.

When you recompile existing programs, try compiling in ANSI mode. ANSI C mandates more thorough error checking, so portability problems are more likely to be flagged by the compiler in this mode. (Bugs are also more likely to be caught.) Many existing programs will compile and execute correctly in ANSI mode with few or no changes.

Pay attention to all warnings produced by the compiler. Most warnings represent potentially problematic program constructs. You should consider warnings to be portability risks.

For an additional level of warnings, compile with the +w1 option. Pay particular attention to the warnings that mention "ANSI migration" issues. These identify most program constructs that are legal but are likely to work differently between pre-ANSI and ANSI compilers.

On HP-UX, use lint, the C program syntax checker, to detect potential portability problems in your program. The lint utility also produces warnings about poor style, lack of efficiency, and inconsistency within the program.

Use the #define, #if, and #ifdef preprocessing directives and typedef declarations to isolate any necessary machine or operating system dependencies.

Declare all variables with the correct types. For example, functions and parameters default to int. On many implementations, pointers and integers are the same size, and the defaults work correctly. However, for maximum portability, the correct types should be used.

Use only the standard C library functions.

Code bit manipulation algorithms carefully to gain independence from machine-specific representations of numeric values. For example, use x & ~3 instead of x & 0xFFFFFFFC to mask the low-order 2 bits to zero.

Avoid absolute addressing.

Examples

The following example illustrates some ways to program for portability. In this example, the include files IEEE.h and floatX.h isolate machine-dependent portions of the code. These include files use the #define and typedef mechanisms to define macro constants and type definitions in the main body of the program.

The main program fmult.c uses the #ifdef preprocessor command to include floatX.h by default. If the option -D IEEE_FLOAT is passed to the compiler, and subsequently the preprocessor, the program will use the IEEE representation for the structure float_rep rather than a machine-dependent representation.

Partial contents of the file IEEE.h:

#define FLT_MAX  3.4028235E38

#define PLUS_INFINITY  0X7F800000

#define MINUS_INFINITY 0XFF800000
     typedef struct {
         unsigned sign : 1;
         unsigned exp : 8;
         unsigned mant : 23;
     } FLOAT_REP;

#define EXP_BIAS 127
     .
     .
     .
Partial contents of the file floatX.h:
#define FLT_MAX  1.70141E38
#define PLUS_INFINITY  0X7FFFFFFE
#define MINUS_INFINITY 0XFFFFFFFE
     typedef struct {
         unsigned sign : 1;
         unsigned mant : 23;
         unsigned exp : 7;
         unsigned exp_sign : 1;
     } FLOAT_REP;

#define EXP_BIAS 0
     .
     .
     .
Partial contents of the file fmult.c:
#ifdef IEEE_FLOAT
#include "IEEE.h"
#else
#include "floatX.h"
#endif
     union  {
         float f;
         FLOAT_REP f_rep;
         FLOAT_INT f_int;
     } float_num;
     float f_mult(float val1, float val2)
     {
         if (val1 > 1.0F && val2 >1.0F) {
             if (val1 > FLT_MAX/val2 ||
                 val2 > FLT_MAX/val1) {
                 float_num.f_int = PLUS_INFINITY;
 
                 return float_num.f;
             }
         .
         .
         .

Practices to Avoid

To make a program portable, you need to minimize machine dependencies. The following are programming practices you should avoid to ensure portability:

General Portability Considerations

This section summarizes some of the general considerations to take into account when writing portable HP C programs. Some of the features listed here may be different on other implementations of C. Differences between Series 300/400 versus workstations and servers implementations are also noted in this section.

Data Type Sizes and Alignments

Table 2-1 in Chapter 2, "Storage and Alignment Comparisons," in the HP C/HP-UX Programmer's Guide shows the sizes and alignments of the C data types on the different architectures.

Differences in data alignment can cause problems when porting code or data between systems that have different alignment schemes. For example, if you write a C program on Series 300/400 that writes records to a file, then read the file using the same program on HP 9000 workstations and servers, it may not work properly because the data may fall on different byte boundaries within the file due to alignment differences. To help alleviate this problem, HP C provides the HP_ALIGN and PACK pragmas, which force a particular alignment scheme, regardless of the architecture on which it is used.

Accessing Unaligned Data

The HP 9000 workstations and servers, like all PA-RISC processors, require data to be accessed from locations that are aligned on multiples of the data size. The C compiler provides an option to access data from misaligned addresses using code sequences that load and store data in smaller pieces, but this option will increase code size and reduce performance. A bus error handling routine is also available to handle misaligned accesses but can reduce performance severely if used heavily.

Here are your specific alternatives for avoiding bus errors:

Whether you use alternative 2 or 3 above depends on your specific code.

The +ubytes option costs significantly less per access than the handler, but it costs you on every access, whether your data is aligned or not, and it can make your code quite a bit bigger. You should use it selectively if you can isolate the routines in your program that may be exposed to misaligned pointers.

There is a performance degradation associated with alternative 3 because each unaligned access has to trap to a library routine. You can use the unaligned_access_count variable to check the number of unaligned accesses in your program. If the number is fairly large, you should probably use 2. If you only occasionally use a misaligned pointer, it is probably better just use the allow_unaligned_data_access handler. There is a stiff penalty per bus error, but it doesn"t cause your program to fail and it won't cost you anything when you operate on aligned data.

The following is a an example of its use within a C program:

extern int unaligned_access_count;
                  /* This variable keeps a count
                     of unaligned accesses. */
 
char arr[]="abcdefgh";
char *cp, *cp2;
int i=99, j=88, k;
int *ip;          /* This line would normally result in a
                     bus error on workstations or servers */
main()
{
   allow_unaligned_data_access();
   cp = (char *)&i;
   cp2 = &arr[1];
   for (k=0; k<4; k++)
       cp2[k] = * (cp+k);
   ip = (int *)&arr[1];
   j = *ip;
   printf("%d\n", j);
   printf("unaligned_access_count is : %d\n", unaligned_access_count);
}
To compile and link this program, enter
cc
filename.c -lhppa
This enables you to link the program with allow_unaligned_data_access() and the int unaligned_access_count that reside in /usr/lib/libhppa.a.

Note that there is a performance degradation associated with using this library since each unaligned access has to trap to a library routine. You can use the unaligned_access_count variable to check the number of unaligned accesses in your program. If the number is fairly large, you should probably use the compiler option.

Checking for Alignment Problems with lint

If invoked with the -s option, the lint command generates warnings for C constructs that may cause portability and alignment problems between Series 300/400 and Series 9000 workstations and servers, and vice versa. Specifically, lint checks for these cases: Note that these are only potential alignment problems. They would cause problems only when a program writes raw files which are read by another system. This is why the capability is accessible only through a command line option; it can be switched on and off.

lint does not check the layout of bit-fields.

Ensuring Alignment without Pragmas

Another solution to alignment differences between systems would be to define structures in such a way that they are forced into the same layout on different systems. To do this, use padding bytes - that is, dummy variables that are inserted solely for the purpose of forcing struct layout to be uniform across implementations. For example, suppose you need a structure with the following definition:
struct S {
    char   c1;
    int    i;
    char   c2;
    double d;
};
An alternate definition of this structure that uses filler bytes to ensure the same layout on Series 300/400 and workstations and servers would look like this:
struct S {
    char   c1;                  /* byte 0 */
    char   pad1,pad2,pad3;      /* bytes 1 through 3 */
    int    i;                   /* bytes 4 through 7 */
    char   c2;                  /* byte 8 */
    char   pad9,pad10,pad11,    /* bytes 9 */
           pad12,pad13,pad14,   /*  through */
           pad15;               /*    15 */
    double d;                   /* bytes 16 through 23 */
};

Casting Pointer Types

Before understanding how casting pointer types can cause portability problems, you must understand how HP 9000 workstations and servers align data types. In general, a data type is aligned on a byte boundary equivalent to its size. For example, the char data type can fall on any byte boundary, the int data type must fall on a 4-byte boundary, and the double data type must fall on an 8-byte boundary. A valid location for a data type would then satisfy the following equation:

  location mod sizeof(data_type) == 0

Consider the following program:

#include <string.h>
#include <stdio.h>
main()
{
  struct chStruct {
    char ch1;                   /* aligned on
                                   an even boundary */
    char chArray[9];            /* aligned on
                                   an odd byte boundary */
  } foo;
 
  int *bar;                     /* must be aligned
                                   on a word boundary */
 
  strcpy(foo.chArray, "1234");  /* place a value
                                   in the ch array */
  bar = (int *) foo.chArray;    /* type cast */
  printf("*bar = %d\n",*bar);   /* display the value */
}
Casting a smaller type (such as char) to a larger type (such as int) will not cause a problem. However, casting a char* to an int* and then dereferencing the int* may cause an alignment fault. Thus, the above program crashes on the call to printf() when bar is dereferenced.

Such programming practices are inherently non-portable because there is no standard for how different architectures reference memory. You should try to avoid such programming practices.

As another example, if a program passes a casted pointer to a function that expects a parameter with stricter alignment, an alignment fault may occur. For example, the following program causes an alignment fault on the HP 9000 workstations and servers:

void main (int argc, char *argv[])
{
    char   pad;
    char   name[8];
 
intfunc((int *)&name[1]);
}
 
int intfunc (int *iptr)
{
    printf("intfunc got passed %d\n", *iptr);
}

Type Incompatibilities and typedef

The C typedef keyword provides an easy way to write a program to be used on systems with different data type sizes. Simply define your own type equivalent to a provided type that has the size you wish to use.

For example, suppose system A implements int as 16 bits and long as 32 bits. System B implements int as 32 bits and long as 64 bits. You want to use 32 bit integers. Simply declare all your integers as type INT32, and insert the appropriate typedef on system A:

typedef long INT32;
The code on system B would be:
typedef int INT32;

Conditional Compilation

Using the #ifdef C preprocessor directive and the predefined symbols __hp9000s300, __hp9000s700, and __hp9000s800, you can group blocks of system-dependent code for conditional compilation, as shown below:
#ifdef  __hp9000s300
    .
    .
    .
  Series 300/400-specific code goes here...
    .
    .
    .

#endif
 

#ifdef  __hp9000s700
    .
    .
    .
  Series 700-specific code goes here...
    .
    .
    .

#endif
 

#ifdef  __hp9000s800
    .
    .
    .
  Series 700/800-specific code goes here...
    .
    .
    .

#endif
If this code is compiled on a Series 300/400 system, the first block is compiled; if compiled on a Series 700 system, the second block is compiled; if compiled on either the Series 700 or Series 800, the third block is compiled. You can use this feature to ensure that a program will compile properly on either Series 300/400 or workstations or servers.

If you want your code to compile only on the Series 800 but not on the 700, surround your code as follows:

#if (defined(__hp9000s800) && !defined(__hp9000s700))
    .
    .
    .
  Series 800-specific code goes here...
    .
    .
    .
#endif

Isolating System-Dependent Code with include Files

#include files are useful for isolating the system-dependent code like the type definitions in the previous section. For instance, if your type definitions were in a file mytypes.h, to account for all the data size differences when porting from system A to system B, you would only have to change the contents of file mytypes.h. A useful set of type definitions is in /usr/include/model.h.

NOTE  If you use the symbolic debugger, xdb, include files used within union, struct, or array initialization will generate correct code. However, such use is discouraged because xdb may show incorrect debugging information about line numbers and source file numbers.

Parameter Lists

On the Series 300/400, parameter lists grow towards higher addresses. On the HP 9000 workstations and servers, parameter lists are usually stacked towards decreasing addresses (though the stack itself grows towards higher addresses). The compiler may choose to pass some arguments through registers for efficiency; such parameters will have no stack location at all.

ANSI C function prototypes provide a way of having the compiler check parameter lists for consistency between a function declaration and a function call within a compilation unit. lint provides an option (-Aa) that flags cases where a function call is made in the absence of a prototype.

The ANSI C <stdarg.h> header file provides a portable method of writing functions that accept a variable number of arguments. You should note that <stdarg.h> supersedes the use of the varargs macros. varargs is retained for compatibility with the pre-ANSI compilers and earlier releases of HP C/HP-UX. See varargs(5) and vprintf(3S) for details and examples of the use of varargs.

The char Data Type

The char data type defaults to signed. If a char is assigned to an int, sign extension takes place. A char may be declared unsigned to override this default. The line:
unsigned char   ch;
declares one byte of unsigned storage named ch. On some non-HP-UX systems, char variables are unsigned by default.

Register Storage Class

The register storage class is supported on Series 300/400 and workstation and servers, and if properly used, can reduce execution time. Using this type should not hinder portability. However, its usefulness on systems will vary, since some ignore it. Refer to the HP-UX Assembler and Supporting Tools for Series 300/400 for a more complete description of the use of the register storage class on Series 300/400.

Also, the register storage class declarations are ignored when optimizing at level 2 or greater on all Series.

Identifiers

To guarantee portable code to non-HP-UX systems, the ANSI C standard requires identifier names without external linkage to be significant to 31 case-sensitive characters. Names with external linkage (identifiers that are defined in another source file) will be significant to six case-insensitive characters. Typical C programming practice is to name variables with all lower-case letters, and #define constants with all upper case.

Predefined Symbols

The symbol __hp9000s300 is predefined on Series 300/400; the symbols __hp9000s800 and __hppa are predefined on Series 700/800; and __hp9000s700 is predefined on Series 700 only. The symbols __hpux and __unix are predefined on all HP-UX implementations. Also, the symbol _PA_RISC2_0 is defined in 32-bit mode and __LP64__ is defined in 64-bit mode.

This is only an issue if you port code to or from systems that also have predefined these symbols.

Shift Operators

On left shifts, vacated positions are filled with 0. On right shifts of signed operands, vacated positions are filled with the sign bit (arithmetic shift). Right shifts of unsigned operands fill vacated bit positions with 0 (logical shift). Integer constants are treated as signed unless cast to unsigned. Circular shifts are not supported in any version of C. For a given type with a size of n bits, the valid shift amount ranges from 0 to n - 1. So, for example, 32 is not valid for an int, but 63 is valid for a long long.

The sizeof Operator

The sizeof operator yields an unsigned long result, as specified in section 3.3.3.4 of the ANSI C standard (X3.159-1989). Therefore, expressions involving this operator are inherently unsigned. Do not expect any expression involving the sizeof operator to have a negative value (as may occur on some other systems). In particular, logical comparisons of such an expression against zero may not produce the object code you expect as the following example illustrates.
main()
{
       int i;
       i = 2;
 if ((i-sizeof(i)) < 0)          /* sizeof(i) is 4,
                                    but unsigned! */
  printf("test less than 0\n");
 else
  printf("an unsigned expression cannot be less than 0\n");
}
When run, this program will print
an unsigned expression cannot be less than 0
because the expression (i-sizeof(i)) is unsigned since one of its operands is unsigned (sizeof(i)). By definition, an unsigned number cannot be less than 0 so the compiler will generate an unconditional branch to the else clause rather than a test and branch.

Bit-Fields

The ANSI C definition does not prescribe bit-field implementation; therefore each vendor can implement bit-fields somewhat differently. This section describes how bit-fields are implemented in HP C.

Bit-fields are assigned from most-significant to least-significant bit on all HP-UX and Domain systems.

On all HP-UX implementations, bit-fields can be signed or unsigned, depending on how they are declared.

On the Series 300/400, a bit-field declared without the signed or unsigned keywords will be signed in ANSI mode and unsigned in compatibility mode by default.

On the workstations and servers, plain int, char, or short bit-fields declared without the signed or unsigned keywords will be signed in both compatibility mode and ANSI mode by default.

On the HP 9000 workstations and servers, and for the most part on the Series 300/400, bit-fields are aligned so that they cannot cross a boundary of the declared type. Consequently, some padding within the structure may be required. As an example,

struct foo
{
        unsigned int   a:3, b:3, c:3, d:3;
        unsigned int   remainder:20;
};
For the above struct, sizeof(struct foo) would return 4 (bytes) because none of the bit-fields straddle a 4 byte boundary. On the other hand, the following struct declaration will have a larger size:
struct foo2
{
        unsigned char   a:3, b:3, c:3, d:3;
        unsigned int    remainder:20;
};
In this struct declaration, the assignment of data space for c must be aligned so it doesn't violate a byte boundary, which is the normal alignment of unsigned char. Consequently, two undeclared bits of padding are added by the compiler so that c is aligned on a byte boundary. sizeof(struct foo2) returns 6 (bytes) on Series 300/400, and 8 on workstations and servers. Note, however, that on Domain systems or when using #pragma HP_ALIGN NATURAL, which uses Domain bit-field mapping, 4 is returned because the char bit-fields are considered to be ints.)

Bit-fields on HP-UX systems cannot exceed the size of the declared type in length. The largest possible bit-field is 32 bits. All scalar types are permissible to declare bit-fields, including enum.

Enum bit-fields are accepted on all HP-UX systems. On Series 300/400 in compatibility mode they are implemented internally as unsigned integers. On workstations and servers, however, they are implemented internally as signed integers so care should be taken to allow enough bits to store the sign as well as the magnitude of the enumerated type. Otherwise your results may be unexpected. In ANSI mode, the type of enum bit-fields is signed int on all HP-UX systems.

Floating-Point Exceptions

HP C on workstations and servers, in accordance with the IEEE standard, does not trap on floating point exceptions such as division by zero. By contrast, when using HP C on Series 300/400, floating-point exceptions will result in the run-time error message Floating exception (core dumped). One way to handle this error on workstations and servers is by setting up a signal handler using the signal system call, and trapping the signal SIGFPE. For details, see signal(2) and signal(5).

For full treatment of floating-point exceptions and how to handle them, see HP-UX Floating-Point Guide.

Integer Overflow

In HP C, as in nearly every other implementation of C, integer overflow does not generate an error. The overflowed number is "rolled over" into whatever bit pattern the operation happens to produce.

Overflow During Conversion from Floating Point to Integral Type

HP-UX systems will report a floating exception - core dumped at run time if a floating point number is converted to an integral type and the value is outside the range of that integral type. As with the error described previously under Floating-Point Exceptions , a program to trap the floating-point exception signal (SIGFPE) can be used. See signal(2) and signal(5) for details.

Structure Assignment

The HP-UX C compilers support structure assignment, structure-valued functions, and structure parameters. The structs in a struct assignment s1=s2 must be declared to be the same struct type as in:
struct s  s1,s2;
Structure assignment is in the ANSI standard. Prior to the ANSI standard, it was a BSD extension that some other vendors may not have implemented.

Structure-Valued Functions

Structure-valued functions support storing the result in a structure:
s = fs();
All HP-UX implementations allow direct field dereferences of a structure-valued function. For example:
x = fs().a;
Structure-valued functions are ANSI standard. Prior to the ANSI standard, they were a BSD extension that some vendors may not have implemented.

Dereferencing Null Pointers

Dereferencing a null pointer has never been defined in any C standard. Kernighan and Ritchie's The C Programming Language and the ANSI C standard both warn against such programming practice. Nevertheless, some versions of C permit dereferencing null pointers.

Dereferencing a null pointer returns a zero value on all HP-UX systems. The workstations and servers C compiler provides the -z compile line option, which causes the signal SIGSEGV to be generated if the program attempts to read location zero. Using this option, a program can "trap" such reads.

Since some programs written on other implementations of UNIX rely on being able to dereference null pointers, you may have to change code to check for a null pointer. For example, change:

if (*ch_ptr != "\0")
to:
if ((ch_ptr != NULL) && *ch_ptr != "\0")
Writes of location zero may be detected as errors even if reads are not. If the hardware cannot assure that location zero acts as if it was initialized to zero or is locked at zero, the hardware acts as if the -z flag is always set.

Expression Evaluation

The order of evaluation for some expressions will differ between HP-UX implementations. This does not mean that operator precedence is different. For instance, in the expression:
x1 = f(x) + g(x) * 5;
f may be evaluated before or after g, but g(x) will always be multiplied by 5 before it is added to f(x). Since there is no C standard for order of evaluation of expressions, you should avoid relying on the order of evaluation when using functions with side effects or using function calls as actual parameters. You should use temporary variables if your program relies upon a certain order of evaluation.

Variable Initialization

On some C implementations, auto (non-static) variables are implicitly initialized to 0. This is not the case on HP-UX and it is most likely not the case on other implementations of UNIX. Don't depend on the system initializing your local variables; it is not good programming practice in general and it makes for nonportable code.

Conversions between unsigned char or unsigned short and int

All HP-UX C implementations, when used in compatibility mode, are unsigned preserving. That is, in conversions of unsigned char or unsigned short to int, the conversion process first converts the number to an unsigned int. This contrasts to some C implementations that are value preserving (that is, unsigned char terms are first converted to char and then to int before they are used in an expression).

Consider the following program:

main()
{
 int i = -1;
 unsigned char uc = 2;
 unsigned int ui = 2;
 
 if (uc > i)
  printf("Value preserving\n");
 else
  printf("Unsigned preserving\n");
 if (ui < i)
  printf("Unsigned comparisons performed\n");
}
On HP-UX systems in compatibility mode, the program will print:
Unsigned preserving
Unsigned comparisons performed
In contrast, ANSI C specifies value preserving; so in ANSI mode, all HP-UX C compilers are value preserving. The same program, when compiled in ANSI mode, will print:
Value preserving
Unsigned comparisons performed

Temporary Files ($TMPDIR)

All HP-UX C compilers produce a number of intermediate temporary files for their private use during the compilation process. These files are normally invisible to you since they are created and removed automatically. If, however, your system is tightly constrained for file space these files, which are generated in /var/tmp by default, may exceed space requirements. By assigning another directory to the TMPDIR environment variable you can redirect these temporary files. See the cc manual page for details.

Input/Output

Since the C language definition provides no I/O capability, it depends on library routines supplied by the host system. Data files produced by using the HP-UX calls write(2) or fwrite(3) should not be expected to be portable between different system implementations. Byte ordering and structure packing rules will make the bits in the file system-dependent, even though identical routines are used. When in doubt, move data files using ASCII representations (as from printf(3)), or write translation utilities that deal with the byte ordering and alignment differences.

Checking for Standards Compliance

In order to check for standards compliance to a particular standard, you can use the lint program with one of the following -D options: For example, the command
lint
-D_POSIX_SOURCE file.c
checks the source file file.c for compliance with the POSIX standard.
  • If you have the HP Advise product, you can also check for C standard compliance using the apex command.

  • Porting to ANSI Mode HP C

    This section describes porting non-ANSI mode HP C programs to ANSI C. Specifically, it discusses:

    ANSI Mode Compile Option (-Aa)

    To compile in ANSI C mode, use the -Aa compile time option.

    By default, beginning at the HP-UX 10.30 operating system release, HP C compilers use -Ae.

    The -w and +e options should not be used at compile time for true ANSI compliance. These options suppress warning messages and allow HP C extensions that are not ANSI conforming.

    HP C Extensions to ANSI C (+e)

    There are a number of HP C extensions enabled by the +e option in ANSI mode: These are the only HP C extensions that require using the +e option.

    When coding for portability, you should compile your programs without the +e command line option, and rewrite code that causes the compiler to generate messages related to HP C extensions.

    const and volatile Qualifiers

    HP C supports the ANSI C const and volatile keywords used in variable declarations. These keywords qualify the way in which the compiler treats the declared variable.

    The const qualifier declares variables whose values do not change during program execution. The HP C compiler generates error messages if there is an attempt to assign a value to a const variable. The following declares a constant variable pi of type float with an initial value of 3.14:

    const float pi = 3.14;
    A const variable can be used like any other variable. For example:
    area = pi * (radius * radius);
    But attempting to assign a value to a const variable causes a compile error:
    pi = 3.1416;  /* This causes an error. */
    Only obvious attempts to modify const variables are detected. Assignments made using pointer references to const variables may not be detected by the compiler.

    However, pointers may be declared using the const qualifier. For example:

    char *const prompt = "Press return to continue> ";
    An attempt to reassign the const pointer prompt causes a compiler error. For example:
    prompt = "Exiting program."; /* Causes a compile time error. */
    The volatile qualifier provides a way to tell the compiler that the value of a variable may change in ways not known to the compiler. The volatile qualifier is useful when declaring variables that may be altered by signal handlers, device drivers, the operating system, or routines that use shared memory. It may also prevent certain optimizations from occurring.

    The optimizer makes assumptions about how variables are used within a program. It assumes that the contents of memory will not be changed by entities other than the current program. The volatile qualifier forces the compiler to be more conservative in its assumptions regarding the variable.

    The volatile qualifier can also be used for regular variables and pointers. For example:

    volatile int intlist[100];
    volatile char *revision_level;
    For further information on the HP C optimizer and its assumptions, see Optimizing HP C Programs . For further information on the const and volatile qualifiers see the HP C/HP-UX Reference Manual.

    ANSI Mode Function Prototypes

    Function prototypes are function declarations that contain parameter type lists. Prototype-style function declarations are available only in ANSI mode. You are encouraged to use the prototype-style of function declarations.

    Adding function prototypes to existing C programs yields three advantages:

    Compiling an existing program in ANSI mode yields some of these advantages because of the existence of prototypes in the standard header files. To take full advantage of prototypes in existing programs, change old-style declarations (without prototype) to new style declarations. On HP-UX, the tool protogen (see protogen(1) in the on-line man pages) helps add prototypes to existing programs. For each source file, protogen can produce a header file of prototypes and a modified source file that includes prototype declarations.

    Mixing Old-Style Function Definitions with ANSI Function Declarations

    A common pitfall when mixing prototypes with old-style function definitions is to overlook the ANSI rule that for parameter types to be compatible, the parameter type in the prototype must match the parameter type resulting from default promotions applied to the parameter in the old-style function definition.

    For example:

    void func1(char c);
    void func1(c)
    char c;
    { }
    gets the following message when compiled in ANSI mode:
    Inconsistent parameter list declaration for "func1"
    The parameter type for c in the prototype is char. The parameter type for c in the definition func1 is also char, but it expects an int because it is an old-style function definition and in the absence of a prototype, char is promoted to int.

    Changing the prototype to:

    void func1(int c);
    fixes the error.

    The ANSI C standard does not require a compiler to do any parameter type checking if prototypes are not used. Value parameters whose sizes are larger than 64 bits (8 bytes) will be passed via a short pointer to the high-order byte of the parameter value. The receiving function then makes a copy of the parameter pointed to by this short pointer in its own local memory.

    Function Prototype Considerations

    There are three things to consider when using function prototypes:

    Type Differences between Actual and Formal Parameters

    When a prototype to a function is added, be careful that all calls to that function occur with the prototype visible (in the same context). The following example illustrates problems that can arise when this is not the case:
    func1(){
      float f;
      func2(f);
    }
     
    int func2(float arg1){
        /* body of func2 */
    }
    In the example above, when the call to func2 occurs, the compiler behaves as if func2 had been declared with an old-style declaration int func2(). For an old-style call, the default argument promotion rules cause the parameter f to be converted to double. When the declaration of func2 is seen, there is a conflict. The prototype indicates that the parameter arg1 should not be converted to double, but the call in the absence of the prototype indicates that arg1 should be widened. When this conflict occurs within a single file, the compiler issues an error:
    Inconsistent parameter list declaration for "func2".
    This error can be fixed by either making the prototype visible before the call, or by changing the formal parameter declaration of arg1 to double. If the declaration and call of func2 were in separate files, then the compiler would not detect the mismatch and the program would silently behave incorrectly.

    On HP-UX, the lint(1) command can be used to find such parameter inconsistencies across files.

    Declaration of a Structure in a Prototype Parameter

    Another potential prototype problem occurs when structures are declared within a prototype parameter list. The following example illustrates a problem that may arise:
    func3(struct stname *arg);
    struct stname { int i; };
     
    void func4(void) {
       struct stname s;
       func3(&s);
    }
    In this example, the call and declaration of func3 are not compatible because they refer to different structures, both named stname. The stname referred by the declaration was created within prototype scope. This means it goes out of scope at the end of the declaration of func3. The declaration of stname on the line following func3 is a new instance of struct stname. When conflicting structures are detected, the compiler issues an error:
    types in call and definition of "func3" have incompatible
    struct/union pointer types for parameter "arg"
    This error can be fixed by switching the first two lines and thus declaring struct stname prior to referencing it in the declaration of func3.

    Mixing of const and volatile Qualifiers and Function Prototypes

    Mixing the const and volatile qualifiers and prototypes can be tricky. Note that this section uses the const qualifier for all of its examples; however, you could just as easily substitute the volatile qualifier for const. The rules for prototype parameter passing are the same as the rules for assignments. To illustrate this point, consider the following declarations: These declarations show how successive levels of a type may be qualified. The declaration for actual0 has no qualifiers. The declaration of actual1 has only the top level qualified. The declarations of actual2 and actual3 have two and three levels qualified. When these actual parameters are substituted into calls to the following functions:
    void f0(int **formal0);
    void f1(int **const formal1);
    void f2(int *const *const formal2);
    void f3(const int *const *const formal3);
    The compatibility rules for pointer qualifiers are different for all three levels. At the first level, the qualifiers on pointers are ignored. At the second level, the qualifiers of the formal parameter must be a superset of those in the actual parameter. At levels three or greater the parameters must match exactly. Substituting actual0 through actual3 into f0 through f3 results in the following compatibility matrix:
     
    Table 36: Compatibility Rules for Pointer Qualifiers 
      f0  f1  f2  f3 
    actual0 C C C N
    actual1 C C C N
    actual2 S S C N
    actual3 NS NS N C

    C =compatible S=not compatible, qualifier level two of formal is not a superset of actual parameter. N=not compatible, qualifier level three doesn"t match

    __declspec

    This release of HP C compiler supports __declspec(dllimport) and __declspec(dllexport) keywords. These keywords have the same semantics as in Microsoft Windows compilers and ease porting of applications developed using Microsoft Windows compilers to HP-UX systems. Support of these keywords enhances the performance of shared libraries and relieves the usage of HP_DEFINED_EXTERNAL pragmas and +Oextern= list to hide the non exported symbols.


    NOTE   __declspec() is available only in ANSI extended (-Ae) mode. Use either -Bhidden or -Bhidden_def to enable __declspec.

    Using,

    Example:

    Consider you have a program as sample.c:

      __declspec(dllexport) int export_me;
        int iam_hidden;
      __declspec(dllimport) int export_me_func() { }
        void iam_hidden_func() { }
    When you compile the above sample.c program using the command
      cc -Bhidden sample.c
    the output sample.o the symbols iam_hidden and iam_hidden_func() will be hidden. export_me will not be hidden.

    An example for __declpec(dllexport) option:

    Some of the examples below lists the valid and invalid definitions used with __declspec(dllexport). __declspec(dllexport) can be used only with data or function definitions.
     __declspec(dllexport) int x;        // Ok
     __declspec(dllexport) extern int x; // Ok, extern will be ignored.
     __declspec(dllexport) int x = 5;     // Ok.
     __declspec(dllexport) can not be used in local scope.
       int foo ( void)
       {
        __declspec(dllexport) int x = 5; // Invalid.
       }

    An example for __declspec(dllimport) option:

    Some of the examples below lists the valid and invalid definitions and parameters used with __declspec(dllimport). __declspec(dllimport) can be used only with declarations.
     __declspec(dllimport) can be used only with the declarations.
       extern will be assumed.
     __declspec(dllimport) int x;        // Ok.
     __declspec(dllimport) extern int x;  // Ok.
     __declspec(dllimport) int x = 5;     // Invalid.
    The example below is not a valid usage of function parameters:
       int foo (__declspec(dllimport) int x) { } // Invalid.
    The example below is not a valid usage of function definitions:
       __declspec(dllimport) int foo(int x)
       {
          // This is wrong.
       }

    Using Name Spaces in HP C and ANSI C

    The ANSI standard specifies exactly which names (for example, variable names, function names, type definition names) are reserved. The intention is to make it easier to port programs from one implementation to another without unexpected collisions in names. For example, since the ANSI C standard does not reserve the identifier open, an ANSI C program may define and use a function named open without colliding with the open(2) system call in different operating systems.

    HP Header File and Library Implementation of Name Spaces

    The HP header files and libraries have been designed to support several different name spaces. On HP-UX systems, four name spaces are available:

    The HP library implementation has been designed with the assumption that many existing programs will use more routines than those allowed by the ANSI C standard.

    If a program calls, but does not define, a routine that is not in the ANSI C name space (for example, open), then the library will resolve that reference. This allows a clean name space and backward compatibility.

    The HP header file implementation uses preprocessor conditional compilation directives to select the name space. In non-ANSI mode, the default is the HP-UX name space. Compatibility mode means that virtually all programs that compiled and executed under previous releases of HP C on HP-UX continue to work as expected. The following table provides information on how to select a name space from a command line or from within a program using the defined libraries.
     
    Table 37: Selecting a Name Space in ANSI Mode 
    When using the name space...  Use command line option...  or #define in source program Platform 
    HP-UX -D_HPUX_SOURCE #define _HPUX_SOURCE HP-UX Only
    XOPEN -D_XOPEN_SOURCE #define _XOPEN_SOURCE HP-UX Only
    POSIX -D_POSIX_SOURCE #define _POSIX_SOURCE HP-UX
    ANSI C default default HP-UX

    In ANSI mode, the default is ANSI C name space. The macro names _POSIX_SOURCE, _XOPEN_SOURCE, and _HPUX_SOURCE may be used to select other name spaces. The name space may need to be relaxed to make existing programs compile in ANSI mode. This can be accomplished by defining the _HPUX_SOURCE macro definition.

    For example, in HP-UX:

    #include <sys/types.h>
    #include <sys/socket.h>
    results in the following compile-time error in ANSI mode because socket.h uses the symbol u_short and u_short is only defined in the HP-UX name space section of types.h:
    "/usr/include/sys/socket.h", line 79: syntax error:
       u_short sa_family;
    This error can be fixed by adding -D_HPUX_SOURCE to the command line of the compile.

    Silent Changes for ANSI C

    Non-ANSI mode HP C is different from ANSI mode HP C in ways that generally go unnoticed. On HP-UX, many of these silent differences can be found by running the lint(1) program. The following list provides some of these silent changes: Note that differences in promotion rules can occur under the following conditions:
  • Floating-point expressions with float operands may be computed as float precision in ANSI mode. In non-ANSI mode they will always be computed in double precision.
  • Initialization rules are different in some cases when braces are omitted in an initialization.
  • Unsuffixed integer constants may have different types. In non-ANSI mode, unsuffixed constants have type int. In the ANSI mode, unsuffixed constants less than or equal to 2147483647 have type int. Constants larger than 2147483647 have type unsigned. For example:
  • -2147483648
    has type unsigned in the ANSI mode and int in non-ANSI mode. The above constant is unsigned in the ANSI mode because 2147483648 is unsigned, and the - is a unary operator.
  • Empty tag declarations in a block scope create a new struct instance in ANSI mode. The term block scope refers to identifiers declared inside a block or list of parameter declarations in a function definition that have meaning from their point of declaration to the end of the block. In the ANSI mode, it is possible to create recursive structures within an inner block. For example:
  • struct x { int i; };
    { /* inner scope */
      struct x;
      struct y { struct x *xptr; };
      struct x { struct y *yptr; };
    }
  • In ANSI mode, the inner struct x declaration creates a new version of the structure type which may then be referred to by struct y. In non-ANSI mode, the struct x; declaration refers to the outer structure.
  • On Series workstations and servers, variable shifts (<< or >>) where the right operand has a value greater than 31 or less than 0 will no longer always have a result of 0. For example,
  • unsigned int i,j = 0xffffffff, k = 32;
    i = j >> k;  /* i gets the value 0 in compatibility mode,  */
                 /* 0xffffffff(-1) in ANSI mode.  */

    Porting between HP C and Domain/C

    All HP-UX and Domain computers have ANSI C compilers. Strictly standard-compliant programs are highly portable between all these architectures.

    The following Domain/C extensions are not supported on HP-UX in compatibility mode and in most cases, are not supported in ANSI mode either:

    There are other differences between HP-UX C and Domain/C:

    Porting between HP C and VMS C

    The C language itself is easy to port from VMS to HP-UX for two main reasons: In most cases, HP C (in compatibility mode) is a superset of VMS C. Therefore, porting from VMS to HP-UX is easier than porting in the other direction. The next several subsections describe features of C that can cause problems in porting.


    Core Language Features

    Preprocessor Features

    Compiler Environment

    Language Interoperability

    It is possible to call a routine written in another language from a C program, but you should have a good reason for doing so. Using more than one language in a program that you plan to port to another system will complicate the process. In any case, make sure that the program is thoroughly tested in any new environment.

    If you do call another language from C, you will have the other language's anomalies to consider plus possible differences in parameter passing. Since all HP-UX system routines are C programs, calling programs written in other languages should be an uncommon event. If you choose to do so, remember that C passes all parameters by value except arrays and structures. The ramifications of this depend on the language of the called function.
     
    Table 38: C Interfacing Compatibility 
    HP-UX Pascal  FORTRAN 
    char none byte
    unsigned char char character (could reside on an odd boundary and cause a memory fault)
    char * (string) none none
    unsigned char * (string) PAC+chr(0) (PAC = packed array[1..n] of char) Array of char+char(0)
    short (int) -32768..32767 (shortint on Series 700/800) integer*2
    unsigned short (int) BIT16 on Series 700/800; none on Series 300/400 (0..65535 will generate a 16-bit value only if in a packed structure) none
    int integer integer (*4)
    long (int) (ILP32) integer integer (*4)
    unsigned (int) none none
    float real real (*4)
    double longreal real*8
    long double

    long double is available only in ANSI mode.

    none real*16
    type* (pointer) ^var, pass by reference, or use anyvar none
    &var (address) addr(var) (requires $SYSPROG$) none
    *var (deref) var^ none
    struct record (cannot always be done; C and Pascal use different packing algorithms) structure
    union record case of union

    Calling FORTRAN

    You can compile FORTRAN functions separately by putting the functions you want into a file and compiling it with the -c option to produce a .o file. Then, include the name of this .o file on the cc command line that compiles your C program. The C program can refer to the FORTRAN functions by the names they are declared by in the FORTRAN source.

    Remember that in FORTRAN, parameters are usually passed by reference (except CHARACTER parameters on Series 700/800, which are passed by descriptor), so actual parameters in a call from C must be pointers or variable names preceded by the address-of operator (&).

    The following program uses a FORTRAN block data subprogram to initialize a common area and a FORTRAN function to access that area:

          double precision function get_element(i,j)
          double precision array
          common /a/array(1000,10)
          get_element = array(i,j)
          end
     
          block data one
          double precision array
          common /a/array(1000,10)
    C Note how easily large array initialization is done.
          data array /1000*1.0,1000*2.0,1000*3.0,1000*4.0,1000*5.0,
         *  1000*6.0,1000*7.0,1000*8.0,1000*9.0,1000*10.0/
          end
    The FORTRAN function and block data subprogram contained in file xx.f are compiled using f77 -c xx.f.

    The C main program is contained in file x.c:

    main()
    {
    int i;
     
    extern double get_element(int *, int *);
     
      for (i=1; i <= 10; i++)
          printf("element = %f\n", get_element(&i,&i));
    }
    The C main program is compiled using cc -Aa x.c xx.o.

    Another area for potential problems is passing arrays to FORTRAN subprograms. An important difference between FORTRAN and C is that FORTRAN stores arrays in column-major order whereas C stores them in row-major order (like Pascal).

    For example, the following shows sample C code:

    int i,j;
    int array[10][20];
     
    for (i=0; i<10; i++) {
        for (j=0; j<20; j++)   /* Here the 2nd dimension
                                 varies most rapidly */
        array [i][j]=0;
    }
    Here is similar code for FORTRAN:
    integer array (10,20)
     
    do J=1,20
      do I=1,10       !Here the first dimension varies most rapidly
        array(I,J)=0
      end do
    end do
    Therefore, when passing arrays from FORTRAN to C, a C procedure should vary the first array index the fastest. This is shown in the following example in which a FORTRAN program calls a C procedure:
    integer array (10,20)
     
    do j=1,20
      do i=1,10
        array(i,j)=0
      end do
    end do
    call cproc (array)
        .
        .
        .
    cproc (array)
    int array [][];
     
    for (j=1; j<20; j++) {
        for (i=1; i<20; i++)   /* Note that this is the reverse from
                                 how you would normally access the
                                 array in C as shown above */
        array [i][j]= ...
    }
            .
            .
            .
    There are other considerations as well when passing arrays to FORTRAN subprograms.

    It should be noted that a FORTRAN main should not be linked with cc.

    Calling Pascal

    Pascal gives you the choice of passing parameters by value or by reference (var parameters). C passes all parameters (other than arrays and structures) by value, but allows passing pointers to simulate pass by reference. If the Pascal function does not use var parameters, then you may pass values just as you would to a C function. Actual parameters in the call from the C program corresponding to formal var parameters in the definition of the Pascal function should be pointers.

    Arrays correlate fairly well between C and Pascal because elements of a multidimensional array are stored in row-major order in both languages. That is, elements are stored by rows; the rightmost subscript varies fastest as elements are accessed in storage order.

    Note that C has no special type for boolean or logical expressions. Instead, any integer can be used with a zero value representing false, and non-zero representing true. Also, C performs all integer math in full precision (32-bit); the result is then truncated to the appropriate destination size.

    To call Pascal procedures from C on the HP 9000 workstations and servers, a program may first have to call the Pascal procedure U_INIT_TRAPS. See the HP Pascal Programmer's Guide for details about the TRY/RECOVER mechanism.

    As true of FORTRAN mains, a Pascal main should not be linked with cc.

    The following source is the Pascal module:

       module a;
     export
      function cfunc : integer;
      function dfunc : integer;
     
     implement
      function cfunc : integer;
       var x : integer;
     
       begin
        x := MAXINT;
        cfunc := x;
       end;
     
      function dfunc : integer;
       var x : integer;
     
       begin
        x := MININT;
        dfunc := x;
       end;
       end.
    The command line for producing the Pascal relocatable object is
    $ pc -c pfunc.p
    The command line for compiling the C main program and linking the Pascal module is
    $ cc x.c pfunc.o -lcl
    The following output results:
    2147483647
    -2147483648