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.
http://docs.hp.com/hpux/development/
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.
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;
}
.
.
.
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.
Here are your specific alternatives for avoiding bus errors:
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 -lhppaThis 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.
struct s1 { char c; long l; };
lint issues the warning:
warning: alignment of struct "s1" may not be portableAlignment of structures and simple types. For example, in the following code, the nested struct would align on a 2-byte boundary on Series 300/400 and an 8-byte boundary on HP 9000 workstations and servers:
struct s3 { int i; struct { double d; } s; };
In this case, lint issues this warning about alignment:
warning: alignment of struct "s3" may not be portableEnd padding of structures. Structures are padded to the alignment of the most-restrictive member. For example, the following code would pad to a 2-byte boundary on Series 300/400 and a 4-byte boundary for HP 9000 workstations and servers:
struct s2 { int i; short s; };
In this case, lint issues the warning:
warning: trailing padding of struct/union "s2" may not be portableNote 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.
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 */
};
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);
}
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;
#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... . . . #endifIf 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
| 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. |
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.
unsigned char ch;declares one byte of unsigned storage named ch. On some non-HP-UX systems, char variables are unsigned by default.
Also, the register storage class declarations are ignored when
optimizing at level 2 or greater on all Series.
This is only an issue if you port code to or from systems that also
have predefined these symbols.
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 0because 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 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.
For full treatment of floating-point exceptions and how to handle them,
see HP-UX Floating-Point Guide.
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.
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 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.
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.
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 performedIn 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
lint -D_POSIX_SOURCE file.cchecks the source file file.c for compliance with the POSIX standard.
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.
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.
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.
Adding function prototypes to existing C programs yields three advantages:
struct s
{
int i;
}
int old_way(x)
struct s x;
{
/* Function body using the old method for
declaring function parameter types
*/
}
int new_way(struct s x)
{
/* Function body using the new method for
declaring function parameter types
*/
}
/* The functions "old_way" and "new_way" are
both called later on in the program.
*/
old_way(1); /* This call compiles without complaint. */
new_way(1); /* This call gives an error. */
In this example, the function new_way gives an error because the
value being passed to it is of type int instead of type struct
x.
More efficient parameter passing in some cases. Parameters of type float are not converted to double. For example:
void old_way(f)
float f;
{
/* Function body using the old method for
declaring function parameter types
*/
}
void new_way(float f)
{
/* Function body using the new method for
declaring function parameter types
*/
}
/* The functions "old_way" and "new_way" are
both called later on in the program.
*/
float g;
old_way(g);
new_way(g);
In the above example, when the function old_way is called, the
value of g is converted to a double before being passed.
In ANSI mode, the old_way function then converts the value back
to float. When the function new_way is called, the float
value of g is passed without conversion.
Automatic conversion of function arguments, as if by assignment. For example, integer parameters may be automatically converted to floating point.
/* Function declaration using the new method for declaring function parameter types */ extern double sqrt(double); /* The function "sqrt" is called later on in the program. */ sqrt(1);In this example, any value passed to sqrt is automatically converted to double. 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.
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.
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.
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.
/* pointer to pointer to int */ int **actual0;
/* const pointer to pointer to int */ int **const actual1;
/* const pointer to const pointer to int */ int *const *const actual2;
/* const pointer to const pointer to const int */ const int *const *const actual3;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:
| 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
| NOTE | __declspec() is available only in ANSI extended (-Ae) mode. Use either -Bhidden or -Bhidden_def to enable __declspec. |
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.cthe output sample.o the symbols iam_hidden and iam_hidden_func() will be hidden. export_me will not be hidden.
__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.
}
__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.
}
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.
| 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.
main(){
unsigned short us = 1;
int i = -2;
printf("%s\n",(i+us)>0 ? "non-ANSI mode" : "ANSI mode");
}
Note that differences in promotion rules can occur under the following
conditions:
-2147483648has 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.
struct x { int i; };
{ /* inner scope */
struct x;
struct y { struct x *xptr; };
struct x { struct y *yptr; };
}
unsigned int i,j = 0xffffffff, k = 32; i = j >> k; /* i gets the value 0 in compatibility mode, */ /* 0xffffffff(-1) in ANSI mode. */
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:
HP-UX C Floating-Point Types
Format
Approximate Range of |x| Approximate Precision
--------------------------------------------------------------------------------------------
float
1.17E-38 to 3.40E38
7 decimal digits
double
2.2E308 to 1.8E308
16 decimal digits
long double 3.36E-4932 to 1.19E4932
31 decimal digits
typedef long t; unsigned t x;is permitted on VMS. This is permitted only in compatibility mode on Series 300/400; it is not allowed in ANSI C mode on any HP-UX system. To accomplish this on HP 9000 workstations and servers, change the typedef to include the type specifier:
typedef unsigned long t; t x;Or use a #define:
#define t long unsigned t x;
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.
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/ endThe 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 doTherefore, 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.
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.pThe command line for compiling the C main program and linking the Pascal module is
$ cc x.c pfunc.o -lclThe following output results:
2147483647 -2147483648