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

 

HP C

HP C
Language Reference Manual


Previous Contents Index

2.12 Forward References

Once declared, identifiers can be used freely. Using an identifier before its declaration is called a forward reference, and results in an error, except in the following cases:

  • When a goto statement refers to a statement label before the label's declaration
  • When a structure, union, or enumeration tag is used before it is declared

Here are some examples of valid and invalid forward references:


int a; 
main () 
{ 
 int b = c;           /*  Forward reference to c -- illegal         */ 
 int c = 10; 
 glop x = 1;          /*  Forward reference to glop type -- illegal */ 
 typedef int glop; 
 goto test;           /*  Forward reference to statement label -- 
                          legal                                     */ 
test:  
 if (a > 0 ) b = TRUE; 
} 

The following example shows the use of a structure tag in a forward reference:


struct s 
  { struct t *pt };    /*  Forward reference to structure t         */ 
.                      /* (Note that the reference is preceded      */ 
.                      /* by the  struct keyword to resolve         */ 
.                      /* potential ambiguity)                      */ 
struct t 
   { struct s *ps }; 

2.13 Tags

Tags can be used with structures, unions, or enumerated types as a means of referring to the structure, union, or enumerated type elsewhere in the program. Once a tag is included in the declaration of a structure, union, or enumerated type, it can specify the declared structure, union, or enumerated type anywhere the declaration is visible.

The following code fragment shows the use of a structure tag, a union tag, and an enumerated type tag:


struct tnode {                 /*  Initial declaration --             */ 
                               /*  tnode is the structure tag         */ 
 int count; 
 struct tnode *left, *right;   /*  tnode's members referring to tnode */ 
 union datanode *p;            /*  forward reference to union type is 
                                   declared below                     */ 
}; 
 
 
union datanode {               /*  Initial declaration --             */ 
                               /*  datanode is the union tag          */ 
 int ival; 
 float fval; 
 char *cval; 
} q = {5}; 
                  
 
enum color { red, blue, green };/*  Initial declaration --            */ 
.                               /*  color is the enumeration tag      */ 
. 
. 
struct tnode x;                /*   tnode tag is used to declare x    */ 
enum color z = blue;           /*   color tag declares z to be of 
                                    type color;  z is also 
                                    initialized to blue               */ 

As shown in the previous example, once a tag is declared it can be used to reference other structure, union, or enumerated type declarations in the same scope without fully redefining the object.

Tags can be used to form an incomplete type if they occur before the complete declaration of a structure or union. Incomplete types do not specify the size of the object; therefore, a tag introducing an incomplete type can only be used when the size of the object is not needed. To complete the type, another declaration of the tag in the same scope must define the object completely. The following example shows how a subsequent definition completes the incomplete declaration of the structure type s:


struct s;            /*  Tag s used in incomplete type declaration */ 
struct t { 
  struct s *p;     
}; 
struct s { int i; };/*  struct s definition completed              */ 

Section 2.6 describes the concept of an incomplete type.

Consider the following declarations:


struct tag; 
 
union tag; 

These declarations specify a structure or union type and declare a tag visible only within the scope of the declaration. The declaration specifies a new type distinct from any other type with the same tag in an enclosing scope (if any).

The following example shows the use of prior tag declarations to specify a pair of mutually-referential structures:


struct s1 { struct s2 *s2p; /*...*/ };  /* D1  */ 
struct s2 { struct s1 *s1p; /*...*/ };  /* D2  */ 

If s2 was declared as a tag in an enclosing scope, the declaration D1 would refer to s2, not to the tag s2 declared in D2. To eliminate this context sensitivity, the following declaration can be inserted ahead of D1:


struct s2; 

This declares a new tag s2 in the inner scope; the declaration D2 then completes the specification of the type.

2.14 lvalues and rvalues

An rvalue is the value of an expression, such as 2, or x + 3 , or (x + y) * (a - b) . rvalues are not allocated storage space. Examples of rvalues are the numbers 0 and 1 in the following code fragment:


if (x > 0) 
   { 
     y += 1; 
   } 
x = *y;        /*  The value pointed to by y is assigned to x   */ 

The identifiers x and y are objects with allocated storage. The pointer to y holds an lvalue.

An lvalue is an expression that describes the location of an object used in the program. The location of the object is the object's lvalue, and the object's rvalue is the value stored at the location described by the lvalue. The following operators always produce lvalues:


[] 
* 
-> 

The dot operator ( . ) can, and usually does, produce an lvalue but it does not have to do so. For example, f().m is not an lvalue.

A modifiable lvalue is an lvalue that does not have array type, an incomplete type, a const-qualified type, or, if it is a structure or union, has no member with const-qualified type.

2.15 Name Spaces

Name spaces are identifier classifications based on the context of the identifier's use in the program. Name spaces allow the same identifier to simultaneously stand for an object, statement label, structure tag, union member, and enumeration constant. Simultaneous use of an identifier in the same scope for two different entities without ambiguity is possible only if the identifiers are in different name spaces. The context of the identifier's use resolves the ambiguity over which of the identically named entities is desired.

There are four different name spaces:

  • Statement labels
  • Structure, union, and enumeration tags
  • Each structure and union member set
  • Other identifiers (variables, functions, type definitions, and enumeration constants)

For example, the identifier flower can be used in one block to stand for both a variable and an enumeration tag, because variables and tags are in different name spaces. Subsequently, an inner block can redefine the variable flower without disturbing the enumeration tag flower. Therefore, when using the same identifier for various purposes, analyze the name space and scope rules governing the identifier. Section 2.3 presents the scope rules.

A structure, union, and enumeration member name can be common to each of these objects at the same time. The use of the structure, union, or enumeration name in the reference to the member resolves any ambiguity about which identifier is meant. However, the structure, union, or enumeration tag must be unique, since the tags of these three object types share the same name space.

2.16 Preprocessing

The translation of a C program occurs in several phases. Normally, when the compiler is started, several events occur before the actual compiler starts:

  1. Trigraph sequences (if any) are replaced by single-character internal representations.
  2. Each occurrence of a new-line character immediately preceded by a backslash character is deleted and the following line is spliced to form one logical line.
  3. The source file is decomposed into preprocessing tokens and sequences of white-space characters. Each comment is replaced by one space character.
  4. Preprocessing directives are executed and preprocessor macros are expanded. Files named in #include preprocessing directives are processed through these four steps recursively.
  5. Each source character set member, and each escape sequence in character constants and string literals is converted to a member of the execution character set.
  6. Adjacent character string literal tokens are concatenated and adjacent wide string literal tokens are concatenated.
  7. The resulting stream of tokens is analyzed and translated.
  8. The linking phase. All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current compilation unit. All such linker output is collected into a program image.

The fourth step is called preprocessing, and is handled by a separate unit of the compiler. Each preprocessor directive appears on a line beginning with a pound sign (#); white space may precede the pound sign. These lines are syntactically independent from the rest of the C source file, and can appear anywhere in the source file. Preprocessor directive lines terminate at the end of the logical line.

It is possible to preprocess a source file without actually compiling the program (see your platform-specific HP C documentation for the available compiler options.) Chapter 8 discusses the preprocessing directives.

2.17 Type Names

In several contexts a type name can or must be specified without an identifier. For example, in a function prototype declaration, the parameters of the function can be declared only with a type name. Also, when casting an object from one type to another, a type name is required without an associated identifier. ( Section 6.4.6 has information on casting, and Section 5.5 has information on function prototypes.) This is accomplished using a type name, which is a declaration for a function or object which omits the identifier.

Table 2-2 shows examples of type names with the associated types they refer to.

Table 2-2 Type Name Examples
Construction Type Name
int int
int * Pointer to int
int *[3] Array of three pointers to int
int (*)[3] Pointer to an array of three ints
int *() Function with no parameter specification returning a pointer to int
int (*) (void) Pointer to function with no parameters returning an int
int (*const []) (unsigned int, ...) Array of an unspecified number of const pointers to functions, each with one parameter that has type unsigned int and an unspecified number of other parameters, returning an int

Table 2-2 also provides good examples of abstract declarators. An abstract declarator is a declarator without an identifier. The characters following the int type name form an abstract declarator in each case. The *, [ ], and ( ) characters all indicate a declarator without naming a specific identifier.


Chapter 3
Data Types

The type of a data object in C determines the range and kind of values an object can represent, the size of machine storage reserved for an object, and the operations allowed on an object. Functions also have types, and the function's return type and parameter types can be specified in the function's declaration.

The following sections discuss these topics:

The selection of a data type for a given object or function is one of the fundamental programming steps in any language. Each data object or function in the program must have a data type, assigned either explicitly or by default. (Chapter 4 discusses the assignment of a data type to an object.) C offers a wide variety of types. This diversity is a strong feature of C, but can be initially confusing.

To help avoid this confusion, remember that C has only a few basic types. All other types are derived combinations of these basic types. Some types can be specified in more than one way; for example, short and short int are the same type. (In this manual, the longest, most specific name is always used.) Type is assigned to each object or function as part of the declaration. Chapter 4 describes declarations in more detail.

Table 3-1 lists the basic data types: integral types (objects representing integers within a specific range), floating-point types (objects representing numbers with a significand part---a whole number plus a fractional number---and an optional exponential part), and character types (objects representing a printable character). Character types are stored as integers.

Note

Enumerated types are also normally classified as integral types, but for the purposes of clarity they are not listed here. See Section 3.6 for more information.

Table 3-1 Basic Data Types
Integral Types Floating Point Types
short int float
signed short int double
unsigned short int long double
int float _Complex (ALPHA, I64)
signed int double _Complex (ALPHA, I64)
unsigned int long double _Complex (ALPHA, I64)
_Imaginary

In HP C, use of the _Imaginary keyword produces a warning, which is resolved by treating it as an ordinary identifier.

 
long int  
signed long int  
unsigned long int  
long long int (ALPHA, I64)  
signed long long int (ALPHA, I64)  
unsigned long long int (ALPHA, I64)  
_Bool  
Integral Character Types  
char  
signed char  
unsigned char  

The integral and floating-point types combined are called the arithmetic types. See Section 3.1 for information about the size and range of integral and floating-point values.

A large variety of derived types can be created from the basic types. Section 3.4 discusses the derived types.

Besides the basic and derived types, there are three keywords that specify unique types: void, enum, and typedef:

  • The void keyword specifies a special type indicating no value, or it can be used with the pointer operator (*) to indicate a generic pointer type. See Section 3.5 for more information on the void type.
  • The enum keyword specifies an integer type of your own design, specifying the acceptable values of the type to a predefined set of named integer constant values. Enumerated types are stored as integers, except in the compiler's RELAXED mode, in which other types are allowed. See Section 3.6 for a detailed description of enumerated types.
  • The typedef keyword specifies a synonym for a type made from one or more basic or derived types. See Section 3.8 for more information on creating type definitions.

There are also the type-qualifier keywords:

  • const, used to prevent write access to an object (see Section 3.7.1)
  • volatile, used to restrict the optimizations that might otherwise be performed on references to an object (see Section 3.7.2)
  • __unaligned (ALPHA, I64), used in pointer definitions, to indicate to the compiler that the data pointed to is not properly aligned on a correct address
  • __restrict (for pointer type only), used to designate a pointer as pointing to a distinct object, thus allowing compiler optimizations to be made (see Section 3.7.4)

Using a qualifying keyword in the type declaration of an object results in a qualified type. See Section 3.7 for general information on type qualifiers.

With such a wide variety of types, operations in a program often need to be performed on objects of different types, and parameters of one type often need to be passed to functions expecting different parameter types. Because C stores different kinds of values in different ways, a conversion must be performed on at least one of the operands or arguments to convert the type of one operand or argument to match that of the other. You can perform conversions explicitly through casting, or implicitly through the compiler. See Section 6.11 for more information on data-type conversions. See Section 2.7 for a description of type compatibility.

See your platform-specific HP C documentation for a description of any implementation-defined data types.

3.1 Data Sizes

An object of a given data type is stored in a section of memory having a discreet size. Objects of different data types require different amounts of memory. Table 3-2 shows the size and range of the basic data types.

Table 3-2 Sizes and Ranges of Data Types
Type Size Range
Integral Types
short int, or signed short int 16 bits - 32768 to 32767
unsigned short int 16 bits 0 to 65535
int or signed int 32 bits - 2147483648 to 2147483647
unsigned int 32 bits 0 to 4294967295
long int, or signed long int (OPENVMS) 32 bits - 2147483648 to 2147483647
long int, or signed long int (TRU64 UNIX) 64 bits - 9223372036854775808 to 9223372036854775807
unsigned long int (OPENVMS) 32 bits 0 to 4294967295
unsigned long int (TRU64 UNIX) 64 bits 0 to 18446744073709551615
signed long long int (ALPHA, I64), signed __int64 (ALPHA, I64) 64 bits - 9223372036854775808 to 9223372036854775807
unsigned long long int (ALPHA, I64), unsigned __int64 (ALPHA, I64) 64 bits 0 to 18446744073709551615
Integral Character Types
char and signed char 8 bits - 128 to 127
unsigned char 8 bits 0 to 255
wchar_t 32 bits 0 to 4294967295
Floating-Point Types (range is for absolute value)
float 32 bits 1.1 x 10 -38 to 3.4 x 10 38
double 64 bits 2.2 x 10 -308 to 1.7 x 10 308
long double (OPENVMS ALPHA, I64) 128 bits 3.4 x 10 -49321 to 1.2 x 10 1049321
long double (OPENVMS VAX, TRU64 UNIX) Same as double Same as double

Derived types can require more memory space.

See your platform-specific HP C documentation for the sizes of implementation-defined data types.


Previous Next Contents Index

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