In C, as in many other programming languages, you must usually declare identifiers before you can use them.
The declarable entities in C are:
translation-unit ::= external-declaration translation-unit external-declaration external-declaration ::= function-definition declaration
declaration ::= declaration-specifiers [init-declarator-list] ; declaration-specifiers ::= storage-class-specifier [declaration-specifiers] type-specifier [declaration-specifiers] type-qualifier [declaration-specifiers] init-declarator-list ::= init-declarator init-declarator-list , init-declarator init-declarator ::= declarator declarator = initializer
A declaration consists of a sequence of specifiers that indicate the linkage, storage duration, and the type of the entities that the declarators denote.
You can declare and initialize objects at the same time using the init-declarator-list syntax. The init-declarator-list is a comma-separated sequence of declarators, each of which may have an initializer.
Function definitions have a slightly different syntax as discussed in
Function
Declarators . Also, note that it is often valid to define a tag (struct,
union,
or enum) without actually declaring any objects.
This new variable or type declared in expr_1 can be used in expr_2, expr_3 and statement_1.
for(int i = 0; i < j; i ++) int i;
Note the lack of a new block opening for the for statement. The C++ compiler accepts this form, with warnings, but the C compiler does not. The difference in the way the stack is handled causes the difference in behavior.
Previously, the C compiler did not emit the source file information for the global typedefs. To correct this, use -y option along with -g when debug info is generated. You can generate debug information by compiling with +Oobjdebug.
int main()
{
int i=5,j;
j=i*i;
printf(*"%d\n",j);
int k=j;
/*This is accepted in the new release of HP C*/
for(struct aa {int a;int b} AA={10,50};AA.a<=AA.b;AA.a++){
/*This is accepted by the new feature */
printf("%d\n",AA.a);}
}
extern int pressure [ ]; /* size will be declared elsewhere */
extern int lines = 66, pages; /* declares two variables,
initializes the first one */
static char private_func (float); /* a function taking a float,
returning a char, not known
outside this unit */
const float pi = 3.14; /* a constant float, initialized */
const float * const pi_ptr = π /* a constant pointer to a constant
float, initialized with an
address constant */
static j1, j2, j3; /* initialized to zero by default */
typedef struct
{double real, imaginary;} Complex; /* declares a type name */
Complex impedance = {47000}; /* second member defaults to zero */
enum color {red=1, green, blue}; /* declares an enumeration tag and
three constants */
int const short static volatile signed
really_Strange = {sizeof "\?"}; /* pretty mixed up */
Invalid Declarations:
int ; /* no identifier */ ; /* no identifier */ int i; j; /* no specifiers for j */
storage-class ::= typedef extern static auto register
The typedef keyword is listed as a storage-class specifier because it is syntactically similar to one.
The keyword extern affects the linkage of a function or object name. If the name has already been declared in a declaration with file scope, the linkage will be the same as in that previous declaration. Otherwise, the name will have external linkage.
The static storage-class specifier may appear in declarations of functions or data objects. If used in an external declaration (either a function or a data object), static indicates that the name cannot be referenced by other translation units. Using the static storage class in this way allows translation units to have collections of local functions and data objects that are not exported to other translation units at link time.
If the static storage class is used in a declaration within a function, the value of the variable is preserved between invocations of that function.
The auto storage-class specifier is permitted only in the declarations of objects within blocks. An automatic variable is one that exists only while its enclosing block is being executed. Variables declared with the auto storage-class are all allocated when a function is entered. Auto variables that have initializes are initialized when their defining block is entered normally. This means that auto variables with initializes are not initialized when their declaring block is not entered through the top.
The register storage class suggests that the compiler store the variable in a register, if possible. You cannot apply the & (address-of) operator to register variables.
If no storage class is specified and the declaration appears in a block, the compiler defaults the storage duration for an object to automatic. If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the extern storage-class specifier.
If no storage class is specified and the declaration appears outside
of a function, the compiler treats it as an externally visible object with
static duration.
type-specifier ::= char short int long long long unsigned signed float double void struct-or-union-specifier enum-specifier typedef-name __Bool
| void |
| char |
| signed char |
| unsigned char |
| short, signed short, short int, or signed short int |
| unsigned short, or unsigned short int |
| int, signed, signed int, or no type specifiers |
| unsigned, or unsigned int |
| long, signed long, long int, or signed long int |
| long long, signed long long, long long int, or signed long long int |
| unsigned long, or unsigned long int |
| unsigned long long, or unsigned long long int |
| float |
| double |
| long double |
| struct-or-union specifier |
| enum-specifier |
| typedef-name |
| __Bool |
If no type specifier is provided in a declaration, the default type is int.
Floating-point types in C are float (32 bits), double (64 bits), and long double (128 bits).
For example: _Bool can be defined as in the following structure declaration:
struct foo {
bool boolval:1;
int i;
}
Since _Bool is defined to take only 0 and 1 as values, this type
of _Bool declaration has some special properties, such as:
_Bool flip_flop = 0; // flip_flop is now false ++flip_flop; // flip_flop is now true ++flip_flop; // flip_flop is true --flip_flop; // flip_flop is now false --flip_flop; // flip_flop is now true
| NOTE | _Bool can be used in ANSI extended (-Ae) and ANSI (-Aa) mode only. |
Boolval = scalarval ? true : false;Boolval would be true(1) or false(0) depending whether scalarval is 1 or 0.
scalarval = Boolval ? 1 : 0;scalarval would be 1 or 0 depending on Boolval being true(1) or false(0).
type-qualifier ::= __thread
This keyword is implemented as an HP specific type qualifier, with the same syntax as type qualifiers const and volatile, but not the same semantics.
Syntax examples:
__thread int var;
int __thread var;
Semantics: Only variables of static duration can be thread-specific. Thread-specific data objects can not be initialized. Pointers of static duration that are not thread-specific may not be initialized with the address of a thread-specific object - assignment is allowed. All global variables, thread-specific or not, are initialized to zero by the linker implicitly.
Only one declaration, for example,
__thread int x;
is allowed in one compilation unit that contributes to the program (including libraries linked into the executable). All other declarations must be strictly references:
extern __thread int x;
Any other redeclarations of this thread-specific x will result in a duplicate definition error at link time.
Even though __thread has the same syntax as a type qualifier,
it does not qualify the type, but is a storage class specification for
the data object. As such, it is type compatible with non-thread-specific
data objects of the same type. That is, a thread-specific data int
is type compatible with an ordinary int, (unlike const
and volatile qualified int).
| NOTE | Use of the __thread keyword in a shared library will prevent that shared library from being dynamically loaded (that is, loaded via an explicit call to shl_load. |
type-qualifier ::= const volatile
The volatile type qualifier directs the compiler not to perform certain optimizations on an object because that object can have its value altered in ways beyond the control of the compiler.
Specifically, when an object's declaration includes the volatile type qualifier, optimizations that would delay any references to (or modifications of) the object will not occur across sequence points. A sequence point is a point in the execution process when the evaluation of an expression is complete, and all side-effects of previous evaluations have occurred.
The volatile type qualifier is useful for controlling access to memory-mapped device registers, as well as for providing reliable access to memory locations used by asynchronous processes.
The const type qualifier informs the compiler that the object will not be modified, thereby increasing the optimization opportunities available to the compiler.
An assignment cannot be made to a constant pointer, but an assignment can be made to the object to which it points. An assignment can be made to a pointer to constant data, but not to the object to which it points. In the case of a constant pointer to constant data, an assignment cannot be made to either the pointer, or the object to which it points.
Type qualifiers may be used alone (as the sole declaration-specifier), or in conjunction with type specifiers, including struct, union, enum, and typedef. Type qualifiers may also be used in conjunction with storage-class specifiers.
Table 14 illustrates various declarations using the const and
volatile
type qualifiers.
| Declaration | Meaning |
|---|---|
| volatile int vol_int; | Declares a volatile int variable. |
| const int *ptr_to_const_int;
int const *ptr_to_const_int; |
Both declare a variable pointer to a constant int. |
| int *const const_ptr_to_int | Declares a constant pointer to a variable int. |
| int *volatile vpi, *pi; | Declares two pointers: vpi is a volatile pointer to an int; pi is a pointer to an int. |
| int const *volatile vpci; | Declares a volatile pointer to a constant int. |
| const *pci; | Declares a pointer to a constant int. Since no type specifier was given, it defaults to int. |
When a type qualifier is used with a variable typed by a typedef name, the qualifier is applied without regard to the contents of the typedef. For example:
typedef int *t_ptr_to_int; volatile t_ptr_to_int vol_ptr_to_int;In the example above, the type of vol_ptr_to_int is volatile t_ptr_to_int, which becomes volatile pointer to int. If the type t_ptr_to_int were substituted directly in the declaration,
volatile int * ptr_to_vol_int;the type would be pointer to volatile int.
Type qualifiers apply to objects, not to types. For example:
typedef int * t; const t *volatile p;In the example above, p is a volatile pointer to a const pointer to int. volatile applies to the object p, while const applies to the object pointed to by p. The declaration of p can also be written as follows:
t const *volatile p;If an aggregate variable such as a structure is declared volatile, all members of the aggregate are also volatile.
If a pointer to a volatile object is converted to a pointer to a non-volatile type, and the object is referenced by the converted pointer, the behavior is undefined.
struct-or-union
specifier ::=
struct-or-union
[identifier] { struct-declaration-list }
struct-or-union
identifier
struct-or-union ::=
struct
union
struct-declaration-list ::=
struct-declaration
struct-declaration-list
struct-declaration
struct-declaration ::=
specifier-qualifier-list
struct-declarator-list;
specifier-qualifier-list ::=
type-specifier
[specifier-qualifier-list]
type-qualifier
[specifier-qualifier-list]
struct-declarator-list ::=
struct-declarator
struct-declarator-list
, struct-declarator
struct-declarator ::=
declarator
[declarator]
: constant-expression
Members are placed in physical storage in the same order as they are declared in the definition of the structure. A member's offset is the distance from the start of the structure to the beginning of the member. The compiler inserts pad bytes as necessary to insure that members are properly aligned. For example, if a char member is followed by a float member, one or more pad bytes may be inserted to insure that the float member begins on an appropriate boundary.
See Data Storage and Alignment for more information.
Unions are like structures except that all members of a union have a zero offset from the beginning of the union. In other words, the members overlap. Unions are a way to store different type of objects in the same memory location.
A declarator for a member of a structure or union may occupy a specified number of bits. This is done by following the declarator with a colon and a constant non-negative integral expression. The value of the expression indicates the number of bits to be used to hold the member. This type of member is called a bit-field. Only integral type specifiers are allowed for bit-field declarators.
In structures, bit-fields are placed into storage locations from the most significant bits to the least significant bits. Bit-fields that follow one another are packed into the same storage words, if possible. If a bit-field will not fit into the current storage location, it is put into the beginning of the next location and the current location is padded with an unnamed field.
A colon followed by an integer constant expression indicates that the compiler should create an unnamed bit-field at that location. In addition, a colon followed by a zero indicates that the current location is full and that the next bit-field should begin at the start of the next storage location.
Although bit-fields are permitted in unions (ANSI mode only), they are just like any other members of the union in that they have a zero offset from the beginning of the union. That is, they are not packed into the same word, as in the case of structures. The special cases of unnamed bit-fields and unnamed bit-fields of length zero behave differently with unions; they are simply unnamed members that cannot be assigned to.
The unary address operator (&) may not be applied to bit-fields. This implies that there cannot be pointers to bit-fields nor can there be arrays of bit-fields.
Refer to Chapter 10, "HP C/HP-UX Implementation Topics," in the HP
C/HP-UX Reference Manual for more information on bit-fields.
A struct or union declaration has two parts: the structure body, where the members of the structure are declared (and possibly a tag name associated with them); and a list of declarators (objects with the type of the structure).
Either part of the declaration can be empty. Thus, you can put the structure body declaration in one place, and use the struct type in another place to declare objects of that type.
For example, consider the following declarations:
struct s1 {
int x;
float y;
};
struct s1 obj1, *obj2;
The first example declares only the struct body and its associated
tag name. The second example uses the struct tag to declare two
objects - obj1 and obj2. They are, respectively, a structure
object of type struct s1 and a pointer object, pointing to an
object of type struct s1.
This allows you to separate all the struct body declarations into one place (for example, a header file) and use the struct types elsewhere in the program when declaring objects.
Consider the following example:
struct examp {
float f; /* floating member */
int i; /* integer member */
}; /* no declaration list */
In this example, the structure tag is examp and it is associated
with the structure body that contains a single floating-point quantity
and an integer quantity. Note that no objects are declared after the definition
of the structure's body; only the tag is being defined.
A subsequent declaration may use the defined structure tag:
struct examp x, y[100];This example defines two objects using type struct examp. The first is a single structure named x and the second, y, is an array of structures of type struct examp.
Another use for structure tags is to write self-referential structures. A structure of type S may contain a pointer to a structure of type S as one of its members. Note that a structure can never have itself as a member because the definition of the structure's content would be recursive. A pointer to a structure is of fixed size, so it may be a member. Structures that contain pointers to themselves are key to most interesting data structures. For example, the following is the definition of a structure that is the node of a binary tree:
struct node {
float data; /* data stored at the node */
struct node *left; /* left subtree */
struct node *right; /* right subtree */
};
This example defines the shape of a node type of structure. Note
that the definition contains two members (left and right)
that are themselves pointers to structures of type node.
The C programming rule that all objects must be defined before use is relaxed somewhat for structure tags. A structure can contain a member that is a pointer to an as yet undefined structure. This allows for mutually referential structures:
struct s1 { struct s2 *s2p; };
struct s2 { struct s1 *s1p; };
In this example, structure s1 references the structure tag s2.
When
s1 is declared,
s2 is undefined. This is valid.
Example
struct tag1 {
int m1;
int :16; /* unnamed bit-field */
int m2:16; /* named bit-field; packed into */
/* same word as previous member */
int m3, m4;
}; /* empty declarator list */
union tag2 {
int u1;
int :16;
int u2:16; /* bit-field, starts at offset 0 */
int u3, u4;
} fudge1, fudge2; /* declarators denoting objects
of the union type */
struct tag1 obj1, *obj2; /* use of type "struct tag1",
whose body has been declared above */
enum-specifier ::=
[ type-specifier ] enum
[ identifier ] {enumerator-list}
[ type-specifier ] enum
identifier
enumerator-list ::=
enumerator
enumerator-list
, enumerator
enumerator ::=
enumeration-constant
enumeration-constant
= constant-expression
enumeration-constant ::= identifier
enum color {red, blue, green=5, violet};
defines red as 0, blue as 1, green as 5, and
violet
as 6.
Enumeration constants share the same name space as ordinary identifiers. They have the same scope as the scope of the enumeration in which they are defined. You can also use the int or long type specifier to indicate 4-byte enums, even though 4-byte enums are the default.
The identifier in the enum declaration behaves like the tags used in structure and union declarations. If the tag has already been declared, you can use the tag as a reference to that enumerated type later in the program.
enum color x, y[100];In this example, the color enumeration tag declares two objects. The x object is a scalar enum object, while y is an array of 100 enums.
An enumeration tag cannot be used before its enumerators are declared.
enum color {RED, GREEN, BLUE};
enum objectkind {triangle, square=5, circle}; /* circle == 6 */
long long enum bigger_enum {barge, yacht}; /* 8-byte enum type */
enum default_enum {ERR1, ERR2, ERR3, ERR4};/* 4-byte enum type */
long enum big_enum {STO, ST1, ST2, ST3}; /* 4-byte enum type */
short enum small_enum {cats, dogs}; /* 2-byte enum type */
char enum tiny_enum {alpha, beta}; /* 1-byte enum type */
When mixed in expressions, enums behave exactly as their similarly sized
type counterparts do. That is, an enum behaves like an int,
a long enum acts like a long int, and a short enum
acts like a short int. You will, however, receive a warning message
when you mix enum variables with integer or floating-point types,
or with differently typed enums.
The sizeof() function returns the actual storage allocated
when called with enum-specifier.
| NOTE | enumeration-constants will have the same size as the type specified
in the enumeration declaration.
char enum {a}; /* sizeof(a) returns 1. */
|
declarator ::= [pointer] direct-declarator direct-declarator ::= identifier (declarator) direct-declarator [[constant-expression]] direct-declarator (parameter-type-list) direct-declarator([identifier-list]) pointer ::= * [type-qualifier-list] * [type-qualifier-list] pointer type-qualifier-list ::= type-qualifier type-qualifier-list type-qualifier parameter-type-list ::= parameter-list parameter-list , ... parameter-list ::= parameter-declaration parameter-list , parameter-declaration parameter-declaration ::= declaration-specifiers declarator declaration-specifiers [abstract-declarator] identifier-list ::= identifier identifier-list , identifier
A declarator specifies one identifier and may supply additional type information. When a construction with the same form as the declarator appears in an expression, it yields an entity of the indicated scope, storage class, and type.
If an identifier appears by itself as a declarator, it has the type indicated by the type specifiers heading the declaration.
Declarator operators have the same precedence and associativity as operators appearing in expressions. Function declarators and array declarators bind more tightly than pointer declarators. You can change the binding of declarator operators using parentheses. For example,
int *x[10];is an array of 10 pointers to ints. This is because the array declarator binds more tightly than the pointer declarator. The declaration
int (*x)[10];is a single pointer to an array of 10 ints. The binding order is altered with the use of parentheses.
Some examples:
int *pi; /* pi: Pointer to an int */ int **ppi; /* ppi: Pointer to a pointer to an int */ int *ap[10]; /* ap: Array of 10 pointers to ints */ int (*pa)[10]; /* pa: Pointer to array of 10 ints */ int *fp(); /* fp: Function returning pointer to int */ int (*pf)(); /* pf: Pointer to function returning an int */The binding of * (pointer) declarators is of lower precedence than either [ ] (array) or () (function) declarators. For this reason, parentheses are required in the declarations of pa and pf.
T D[constant-expression];declares D to be an array of type T.
You declare multidimensional arrays by specifying additional array declarators. For example, a 3 by 5 array of integers is declared as follows:
int x[3][5];This notation (correctly) suggests that multidimensional arrays in C are actually arrays of arrays. Note that the [ ] operator groups from left to right. The declarator x[3][5] is actually the same as ((x[3])[5]). This indicates that x is an array of three elements each of which is an array of five elements. This is known as row-major array storage.
You can omit the constant-expression giving the size of an array under certain circumstances. You can omit the first dimension of an array (the dimension that binds most tightly with the identifier) in the following cases:
Following are examples of array declarations:
int x[10]; /* x: Array of 10 integers */
float y[10][20]; /* y: Matrix of 10x20 floats */
extern int z[ ]; /* z: External integer array of undefined
dimension */
int a[ ]={2,7,5,9}; /* a: Array of 4 integers */
int m[ ][3]= { /* m: Matrix of 2x3 integers */
{1,2,7},
{6,6,6} };
Note that an array of type T that is the formal parameter in a
function definition has been converted to a pointer to type T.
The array name in this case is a modifiable lvalue and can appear as the
left operand of an assignment operator. The following function will clear
an array of integers to all zeros. Note that the array name, which is a
parameter, must be a modifiable lvalue to be the operand of the ++
operator.
void clear(a, n)
int a[]; /* has been converted to int * */
int n; /* number of array elements to clear */
{
while(n--) /* for the entire array */
*a++ = 0; /* clear each element to zero */
}
All identifiers having a VM type must be either,
| NOTE | VLA is supported in the ANSI extended (-Ae) mode only. |
Arrays declared with the static or extern storage class specifier cannot have a VM type. But a pointer to an array declared with the static storage class specifier can have a VM type. All identifiers having a VM type have to be an ordinary identifiers and therefore cannot be the members of structures or unions.
extern int n;
int A[n]; // Error - file scope VM type
extern int (*p)[n]; // Error - file scope VM type
int B[100]; // OK - file scope but not VM type
void foo(int m, int C[m]) // OK - function prototype scope VM type
{
typedef int VLA[m][m]; // OK - block scope VM type
int D[m]; // OK - block scope with VM type
static int E[m]; // Error - static specifier in VM type
extern int F[m]; // Error - extern specifier in VM type
int (*q)[m]; // OK - block scope with VM type
extern int (*r)[m]; // Error - extern specifier in VM type
static int (*s)[m] = &B; // OK - static specifier allowed in VM
// type since s is pointer to array
struct tag {
int (*x)[n]; // Error - x not ordinary identifier
int y[n]; // Error - y not ordinary identifier
};
}
A goto statement is not allowed to jump past any declarations of identifiers
having a VM type. A jump within the scope, however, is permitted.
goto L1; // Error -- going INTO scope of VM type
{
int a[n];
a[j] = 4;
L1:
a[j] = 3;
goto L2; // OK, going WITHIN scope of VM type
a[j] = 5;
L2:
a[j] = 6;
}
goto L2; // Error -- going INTO scope of VM type
The size of an object having a VM type is determined and fixed at the point
of that object's declaration and cannot be altered.
int n = 10, vla[n];
n = 20;
printf("%d", sizeof(vla)); // prints 40, not 80
T D (parameter-type-list)or
T D ([identifier-list])declares D to be a function returning type T. A function can return any type of object except an array or a function. However, functions can return pointers to functions or arrays.
If the function declarator uses the form with the parameter-type-list, it is said to be in "prototype" form. The parameter type list specifies the types of, and may declare identifiers for, the parameters of the function. If the list terminates with an ellipsis (,...), no information about the number of types of the parameters after the comma is supplied. The special case of void as the only item in the list specifies that the function has no parameters.
If a function declarator is not part of a function definition, the optional identifier-list must be empty.
Function declarators using prototype form are only allowed in ANSI mode.
Functions can also return structures. If a function returns a structure as a result, the called function copies the resulting structure into storage space allocated in the calling function. The length of time required to do the copy is directly related to the size of the structure. If pointers to structures are returned, the execution time is greatly reduced. (But beware of returning a pointer to an auto struct - the struct will disappear after returning from the function in which it is declared.)
The function declarator is of equal precedence with the array declarator. The declarators group from left to right. The following are examples of function declarators:
int f(); /* f: Function returning an int */ int *fp(); /* fp: Function returning pointer to an int */ int (*pf)(); /* pf: Pointer to function returning an int */ int (*apf[])(); /* apf: Array of pointers to functions */ /* returning int */Note that the parentheses alter the binding order in the declarations of pf and apf in the above examples.
type-name ::= specifier-qualifier-list [abstract-declarator] abstract-declarator ::= pointer [pointer] direct-abstract-declarator direct-abstract-declarator (abstract-declarator ) [direct-abstract-declarator] [ [constant-expression] ] [direct-abstract-declarator] ( [parameter-type-list] )
A type name is a declaration without the identifier specified. For example,
the declaration for an integer is int i. If the identifier is
omitted, only the integer type int remains.
int int int * Pointer to int int () Function returning an int int *() Function returning a pointer to int int (*)() Pointer to function returning an int int [3]; Array of 3 int int *[3]; Array of 3 pointers to int int (*)[3]; Pointer to an array of 3 intThe parentheses are necessary to alter the binding order in the cases of pointer to function and pointer to array. This is because function and array declarators have higher precedence than the pointer declarator.
typedef-name ::= identifier
For example, the following typedef defines a new name for a pointer to an int:
typedef int *pointer;Instead of the identifier pointer actually being a pointer to an int, it becomes the name for the pointer to the int type. You can use the new name as you would use any other type. For example:
pointer p, *ppi;This declares p as a pointer to an int and ppi as a pointer to a pointer to an int.
One of the most useful applications of typedef is in the definition of structure types. For example:
typedef struct {
float real;
float imaginary;
} complex;
The new type complex is now defined. It is a structure with two
members, both of which are floating-point numbers. You can now use the
complex
type to declare other objects:
complex x, *y, a[100];This declares x as a complex, y as a pointer to the complex type and a as an array of 100 complex numbers. Note that functions would have to be written to perform complex arithmetic because the definition of the complex type does not alter the operators in C.
Other type specifiers (that is, void, char, short, int, long, long long, signed, unsigned, float, or double) cannot be used with a name declared by typedef. For example, the following typedef usage is illegal:
typedef long int li; . . . unsigned li x;typedef identifiers occupy the same name space as ordinary identifiers and follow the same scoping rules.
Structure definitions which are used in typedef declarations
can also have structure tags. These are still necessary to have self-referential
structures and mutually referential structures.
typedef unsigned long ULONG; /* ULONG is an unsigned long */ typedef int (*PFI)(int); /* PFI is a pointer to a function */ /* taking an int and returning an int */ ULONG v1; /* equivalent to "unsigned long v1" */ PFI v2; /* equivalent to "int (*v2)(int)" */
`typeof'-construct can be used anywhere a typedef
name can be used. sizeof or __typeof__.
This example declares y as an array of type of what x points to:
__ typeof__ (*x) y[4];This example declares
y as an array of pointers to characters:
__typeof__(__typeof__(char *)[4]) y;
initializer ::=
assignment-expression
{initializer-list}
{initializer-list , }
initializer-list ::=
initializer
initializer-list
, initializer
Objects with static storage duration are initialized at load time. Objects with automatic storage duration are initialized at run-time when entering the block that contains the definition of the object. An initialization of such an object is similar to an assignment statement.
You can initialize a static object with a constant expression. You can initialize a static pointer with the address of any previously declared object of the appropriate type plus or minus a constant.
You can initialize an auto scalar object with an expression. The expression is evaluated at run-time, and the resulting value is used to initialize the object.
When initializing a scalar type, you may optionally enclose the initializer in braces. However, they are normally omitted. For example
int i = {3};
is normally specified as
int i = 3;When initializing the members of an aggregate, the initializer is a brace-enclosed list of initializes. In the case of a structure with automatic storage duration, the initializer may be a single expression returning a type compatible with the structure. If the aggregate contains members that are aggregates, this rule applies recursively, with the following exceptions:
int q [3] [3] [2] = {
{ 1 }
{ 2, 3 }
{ 4, 5, 6 }
};
produces the following layout:
ANSI Mode Compatibility Mode 1 0 0 0 0 0 1 0 2 3 4 5 2 3 0 0 0 0 6 0 0 0 0 0 4 5 6 0 0 0 0 0 0 0 0 0It is advisable to either fully specify the braces, or fully elide all but the outermost braces, both for readability and ease of migration from compatibility mode to ANSI mode.
Because the compiler counts the number of specified initializes, you do not need to specify the size in array declarations. The compiler counts the initializes and that becomes the size:
int x[ ] = {1, 10, 30, 2, 45};
This declaration allocates an array of int called x with a size
of five. The size is not specified in the square brackets; instead, the
compiler infers it by counting the initializes.
As a special case, you can initialize an array of characters with a character string literal. If the dimension of the array of characters is not provided, the compiler counts the number of characters in the string literal to determine the size of the array. Note that the terminating \0 is also counted. For example:
char message[ ] = "hello";This example defines an array of characters named message that contains six characters. It is identical to the following:
char message[ ] = {"h","e","l","l","o","\0"};
You can also initialize a pointer to characters with a string literal:
char *cp = "hello";This declares the object cp as a character pointer initialized to point to the first character of the string "hello".
It is illegal to specify more initializes in a list than are required to initialize the specified aggregate. The one exception to this rule is the initialization of an array of characters with a string literal.
char t[3] = "cat";This initializes the array t to contain the characters c, a, and t. The trailing "\0" character is ignored.
If there are not enough initializes, the remainder of the aggregate is initialized to zero.
Some more examples include:
char *errors[ ] = {
"undefined file",
"input error",
"invalid user"
};
In this example, the array errors is an array of pointers to character
(strings). The array is initialized with the starting addresses of three
strings, which will be interpreted as error messages.
An array with element type compatible with wchar_t (unsigned
int) may be initialized by a wide string literal, optionally enclosed
in braces. Successive characters of the wide string literal initialize
the members of the array. This includes the terminating zero-valued character,
if there is room or if the array is of unknown size.
wchar_t wide_message[ ]=L"x$$z";You initialize structures as you do any other aggregate:
struct{
int i;
unsigned u:3;
unsigned v:5;
float f;
char *p;
} s[ ] = {
{1, 07, 03, 3.5, "cats eat bats" },
{2, 2, 4, 5.0, "she said with a smile"}
};
Note that the object being declared (s) is an array of structures
without a specified dimension. The compiler counts the initializes to determine
the array's dimension. In this case, the presence of two initializes implies
that the dimension of s is two. You can initialize named bit-fields
as you would any other member of the structure.
If the value used to initialize a bit-field is too large, it is truncated to fit in the bit-field.
For example, if the value 11 were used to initialize the 3-bit field u above, the actual value of u would be 3 (the top bit is discarded).
A struct or union with automatic storage duration can also be initialized with a single expression of the correct type.
struct SS { int y; };
extern struct SS g(void);
func()
{
struct SS z = g();
}
When initializing a union, since only one union member
can be active at one time, the first member of the union is taken to be
the initialized member.
The union initialization is only available in ANSI mode.
union {
int i;
float f;
unsigned u:5;
} = { 15 };
(type-name) {initializer-list}
where, type name must specify an object type or an array of unknown size.
The value of the compound literal is that of an unnamed object initialized
by the initializer list. The object has static storage if the compound
literal occurs outside the body of the function, otherwise it has automatic
storage duration associated with the enclosing blocks.
int *p = (int []) {1,2};
In this example, an array of size 2 has been declared, with the first two
elements initialized to 1 and 2. (int [ ]){1,2} represents the compound
literal and it is assigned to a pointer variable p of type int.
struct node
{
int a;
int b;
};
struct node st[] = (struct node[2]){1,2,3,4};
In this example, an array of structures has been initialized with the values
in the initializer list. (struct node){1,2,3,4} is the compound literal
and is assigned to an array of structures st.
int main()
{
foo((int []){1,2,3,4});
}
int foo(int * p)
{
}
In this example, a compound literal is passed as a parameter to the function
foo() instead of creating a temporary variable in the function main()and
then passing it as a parameter to foo(). Compound literals can be passed
as parameters to functions eliminating the need of defining a temporary
variable in the caller function.
int *p = (int [10000]){[999]=20};
This example shows how a particular element[999] in an array of size 10000
can be initialized explicitly.
char *c = (char []){"/tmp/testfile"};
This example is for a compound literal initializes an array of characters.
union u { int x;
int c;
};
union u u1 = (union u) {.c = 10};
This example is for a compound literal having a union type. Where the member-wise
initialization can be done, instead of the old order-wise initialization.
(const float []) {1e0, 1e1};
This example is for a constant compound literal.
struct int_list {
int car;
struct int_list *cdr;
};
struct int_list endless_zeros = {0, &endless_zeros};
Since compound literals are unnamed, a single compound literal cannot be
used to specify a circularly linked object. The above example cannot be
represented in a compound literal form.
drawline((struct point){.x=1, .y=1}, &(struct point){.x=3,.y=3}); /* call */
drawline(struct point, struct point *) { /* definition */}
This example is for structure objects created using compound literal, which
are passed to functions.
struct st{int a[5], b;};
struct st *w = (struct st []) {[3]={1,2},[1]={.a={[3]=1},.b=1}};
This example is for a combination of both array and structure initialization.
Here w[1].a[3] will have a value of 1.
struct st {
int a, b;
char c;
};
struct st s1;
s1 = (struct st) {.a=1, .c='x', .b=10}; // new way
struct st s1 = {1, 10, 'x'}; // old way
The above example using a structure only tries to show how member-wise
initialization can be done for a compound literal and how initializations
would have been done earlier.
int x = (int){5}; // initializing x with 5
int *y = (int *){&x}; // initializing y with the address of x
The purpose of the above example is only to show the other possible ways
of using compound literal.
| NOTE | Compound literal can be used only in ANSI extended (-Ae) mode. |
Designated initializers are of the form:
int a[100] = {1,2,[50]=3,4,5,[23]=6,7};
In this example, designated initializers [50] and [23]are
used to initialize elements in the middle of an array without initializing
all the elements prior to them in the array. The element a[50]
contains the value 3 while a[51]is initialized to 4. Similarly,
a[23]=6 and a[24]=7.
struct node
{
int a;
int b;
char c[10];
};
struct node st = { .b = 1,.c[4] = 1,2,3,.a = 4};
In this example, .b and .a initialize particular members
of the structure st while the designated initializer .c[4] initializes
a particular array element (index 4) of the structure member .c[4]
with 1, c[5] is initialized to 2, and so on.
union u
{
int x;
int c;
};
union u u1 = {.c = 10};
In this example the order of initializing union members need not be followed.
struct st {
int a, b;
char c;
};
struct st s1 = {.a=1, .c='x', .b=10}; // new way
struct st s2 = {1, 10, 'x'}; // old way
The above example using a structure only tries to show how member-wise
initialization can be done and how initializations was done earlier.
struct st{int a[5], b;};
struct st w[] = { [3]={1,2}, [1]={.a={[3]=1},.b=1} };
A combination of both array and structure initialization. Here w[1].a[3]
will have a value of 1.
int a[5] = {[2]=4, [0]=1,2,3};
In the above example, a[2] is initialized to 3 but when compiled
with +O3 and above will initialize a[2] to 4.
| NOTE | Designated initializer are supported only in ANSI extended (-Ae) mode. |
int i, j;
/* ... */
int iarr[] = { i, j, i + j, i - j };
function-definition ::= [declaration-specifiers] declarator [declaration-list] compound-statement
| NOTE | Function prototypes can be used only in ANSI mode. |
Body of the function. You supply the body that executes when the function is called in a single compound statement following the optional declaration-list. Do not confuse definition with declaration, especially in the case of functions. Function definition implies that the above four pieces of information are supplied. Function declaration implies that the function is defined elsewhere.
You can declare formal parameters as structures or unions. When the function is called, the calling function's argument is copied to temporary locations within the called function.
All functions in C may be recursive. They may be directly recursive so the function calls itself or they may be indirectly recursive so a function calls one or more functions which then call the original function. Indirect recursion can extend through any number of layers.
In function definitions that do not use prototypes, any parameters of
type float are actually passed as double, even though
they are seen by the body of the function as floats. When such a function
is called with a float argument, the float is converted back to float on
entry into the function.
| NOTE | In compatibility mode, the type of the parameter is silently changed to double, so the reverse conversion does not take place. |
In a prototype-style definition, such conversions do not take place, and the float is both passed and accessed in the body as a float.
char and short parameters to nonprototype-style function definitions are always converted to type int. This conversion does not take place in prototype-style definitions.
In either case, arrays of type T are always adjusted to pointer to type T, and functions are adjusted to pointers to functions.
Single dimensioned arrays declared as formal parameters need not have their size specified. If the name of an integer array is x, the declaration is as follows:
int x[ ];For multidimensional arrays, each dimension must be indicated by a pair of brackets. The size of the first dimension may be left unspecified.
The storage class of formal parameters is implicitly "function parameter."
A further storage class of register is accepted.
int total(data, n) /* function type, name, formal list */
int data[ ]; /* parameter declarations */
int n;
{
auto int sum = 0; /* local, initialized */
auto int i; /* loop variable */
for(i=0; i<n; ++i) /* range over all elements */
sum += data[i]; /* total the data array */
return sum; /* return the value */
}
This is an example of a function definition without prototypes.
int func1 (p1, p2) /* old-style function definition */
int p1, p2; /* parameter declarations */
{ /* function body starts */
int l1; /* local variables */
l1 = p1 + p2;
return l1;
}
Here is an example of a function definition using prototypes.
char *func2 (void) /* new-style definition */
/* takes no parameters */
{
/* body */
}
int func3 (int p1, char *p2, ...)/* two declared parameters:
p1 & p2 */
/* "..." specifies more,
undeclared parameters
of unspecified type */
{
/* body */ /* to access undeclared
parameters here, use the
functions declared in the
<stdarg.h> header file. */
}
| NOTE | inline is supported only in ANSI extended (-Ae) mode and at optimization levels above +O3. |
inline void foo(int); // Function declaration with inline specifier
main()
{
foo(5);
}
void foo(int x)
{
. . . .
}
Example 2: Using inline in function definition
main()
{
foo(5);
}
inline foo(int x) //Function definition with inline specifier
{
....
}
This keyword is implemented as an HP-specific type qualifier, with the same syntax as const and volatile, but not the same semantics. Syntax examples:
__thread int var; int __thread var;Semantics for the __thread keyword: Only variables of static duration can be thread specific. Thread specific data objects can not be initialized. Pointers of static duration that are not thread specific may not be initialized with the address of a thread specific object - assignment is okay. All global variables, thread specific or not, are initialized to zero by the linker implicitly.
Only one declaration, for example,
__thread int x;is allowed in one compilation unit that contributes to the program (including libraries linked into the executable). All other declarations must be strictly references:
extern __thread int x;Even though __thread has the same syntax as a type qualifier, it does not qualify the type, but is a storage class specification for the data object. As such, it is type compatible with non-thread-specific data objects of the same type. That is, a thread specific data int is type compatible with an ordinary int, (unlike const and volatile qualified int).
Note that use of the __thread keyword in a shared library will
prevent that shared library from being dynamically loaded (that is, loaded
via an explicit call to
shl_load()).
void func(int a, int b)
{
. . .
}
This indicates that the function func() does not return a value.
Likewise, on the calling side, you declare
func() as:
extern void func(int, int);
[storage_class_specifier] [data_type] variable_name [=initial_value];where:
int age; /* integer variable "age" */
int length, width; /* abbreviated declaration of two
variables*/
float ph; /* floating-point variable "ph" */
char a_letter; /* character variable "a_letter" */
int values[10]; /* array of 10 integers named values */
enum days {mon, wed, fri}; /* enumerated variable "days" */
A typedef declaration may appear anywhere a variable declaration may appear and obeys the same scoping rules as a normal declaration. Once declared, a typedef name may be used anywhere that the type is allowed (such as in a declaration, cast operation, or sizeof operation). You can write typedef names in all uppercase so that they are not confused with variable names.
You may not include an initializer with a typedef.
The statement:
typedef long int FOUR_BYTE_INT;makes the name FOUR_BYTE_INT synonymous with long int. The following two declarations are now identical:
long int j; FOUR_BYTE_INT j;
typedef struct {
char month[4];
int day;
int year;
} BIRTHDAY;
typedef char A_LINE[80]; /* A_LINE is an array of
* 80 characters */
#if SMALL_COMPUTER typedef int SHORTINT; typedef long LONGINT; #elif BIG_COMPUTER typedef short SHORTINT; typedef int LONGINT; #endifThis is useful when writing code to run on two computers, a small computer where an int is two bytes, and a large computer where an int is four bytes. Instead of using short, long, and int, you can use SHORTINT and LONGINT and be assured that SHORTINT is two bytes and LONGINT is four bytes regardless of the machine.
typedef float *PTRF, ARRAYF[], FUNCF();This declares three new types called PTRF (a pointer to a float), ARRAYF (an array of floats), and FUNCF (a function returning a float). These typedefs could then be used in declarations such as the following:
PTRF x[5]; /* a 5-element array of pointers to floats */ FUNCF z; /* A function returning a float */
| wrong | right |
|---|---|
typedef char STR[80];
STR string, *ptr;
main()
{
ptr = string;
printf("ptr = %d\n", ptr);
ptr++;
printf("ptr = %d\n", ptr);
}
*** Run-Time Results ***
ptr = 3997696
ptr = 3997776
|
typedef char STR[80];
STR string;
char *ptr;
main()
{
ptr = string;
printf("ptr = %d\n", ptr);
ptr++;
printf("ptr = %d\n", ptr);
}
*** Run-Time Results ***
ptr = 3997696
ptr = 3997697
|
| NOTE | The separate name spaces for goto labels and for each struct, union, or enum definition are part of the ANSI/ISO standard, but not part of the K&R language definition. |
The following example uses the same name, overuse, in four different ways:
int main(void)
{
int overuse; /* normal identifier */
struct overuse { /* tag name */
float overuse; /* member name */
char *p;
} x;
goto overuse;
overuse: overuse = 3; /* label name */
}
struct A {
int x;
float y;
};
struct B {
int x;
float y;
};
The members in struct A are distinct from the members in structB.
# define square(arg) arg * arg
int main(void)
{
...
square:
...
}
Every constant has two properties: value and type.
For example, the constant 15 has value 15 and type int.
The data type assigned to an integer constant is the first in which
it will fit from the list on the right for the constant declaration on
the left:
| Constant | Assigned Data Type |
|---|---|
| decimal (no suffix) | int, long int, unsigned long int |
| octal or hex (no suffix) | int, unsigned int, long, unsigned long |
| letter u or U suffix | unsigned int, unsigned long int |
| letter l or L suffix | long, unsigned long |
| both letters u or U and | unsigned long l or L suffix: |
| letters ll or LL suffix: | long long, unsigned long long |
| both letters u or U and ll or LL suffix: | unsigned long long |
| Decimal | Octal | Hexadecimal |
|---|---|---|
| 3 | 003 | 0x3 |
| 8 | 010 | 0x8 |
| 15 | 017 | 0xF |
| 16 | 020 | 0x10 |
| 21 | 025 | 0x15 |
| -87 | -0127 | -0x57 |
| 187 | 0273 | 0xBB |
| 255 | 0377 | 0xff |
The number may be followed by an f or F, to signify that it is of type float, or by an l or L, to signify that it is of type long double. If the number does not have a suffix, it is of type double even if it can be accurately represented in four bytes.
If the magnitude of a floating-point constant is too great or too small to be represented in a double, the C compiler will substitute a value that can be represented. This substitute value is not always predictable.
You may precede a floating-point constant with the unary plus or minus
operator to make its value positive or negative.
The letter e or E, standing for exponent, is used to separate the two parts.
The floating-point constant 3e2, for instance, is interpreted as 3*(102),
or 300. Likewise, the value -2.5e-4 is interpreted as -2.5/(104),
or -0.00025.
| Constant | Legal or Illegal |
|---|---|
| 3. | legal |
| 35 | legal - interpreted as an integer. |
| 3.141 | legal |
| 3,500.45 | illegal - commas are illegal. |
| .3333333333 | legal |
| 4E | illegal - the exponent must be followed by a number |
| 0.3 | legal |
| -3e2 | legal |
| 4e3.6 | illegal - the exponent must be an integer |
| 3.0E5 | legal |
| +3.6 | legal |
| 0.4E-5 | legal |
The value of a character constant is the integer ISO Latin-1 value of
the character. For example, the value of the constant x is 120.
| Escape Code | Character | What it Does |
|---|---|---|
| \a | Audible alert | Rings the terminal's bell. |
| \b | Backspace | Moves the cursor back one space. |
| \f | Formfeed | Moves the cursor to the next logical page. |
| \n | Newline | Prints a newline. |
| \r | Carriage return | Prints a carriage return. |
| \t | Horizontal tab | Prints a horizontal tab. |
| \v | Vertical tab | Prints a vertical tab. |
| \\ | Backslash | Prints a backslash. |
| \? | Question mark | Prints a question mark. |
| \' | Single quote | Prints a single quote. |
| \" | Double quote | Prints a double quote. |
The escape sequences for octal and hexadecimal numbers are commonly
used to represent characters. For example, if ISO Latin-1 representations
are being used, the letter a may be written as \141 or
\x61
and Z as \132 or \x5A. This syntax is most frequently
used to represent the null character as \0. This is exactly equivalent
to the numeric constant zero (0). When you use the octal format, you do
not need to include the zero prefix as you would for a normal octal constant.
For example, the following assignments are legal:
{
char x; /* 1-byte integer */
unsigned short int si; /* 2-byte integer */
unsigned long int li; /* 4-byte integer */
/* the following two assignments are portable: */
x = "j"; /* 1-byte character constant */
li = L"j"; /* 4-byte wide char constant */
/* the following two assignments are not portable,
and are not recommended: */
si = "ef"; /* 2-character constant */
li = "abcd"; /* 4-character constant */
}
The variable si is assigned the value of e and f,
where each character takes up 8 bits of the 16-bit value. The HP C compiler
places the last character in the rightmost (least significant) byte. Therefore,
the constant ef will have a hexadecimal value of 6566. Since the
order in which bytes are assigned is machine dependent, other machines
may reverse the order, assigning f to the most significant byte.
In that case, the resulting value would be 6665. For maximum portability,
do not use multi-character constants. Use character arrays instead.
"A short string"becomes an array with 15 elements:

Like a character constant, a string constant can begin with the letter L to indicate that it is a string constant in an extended character set.
To span a string constant over more than one line, use the backslash character (\), also called the continuation character. The following, for instance, is legal:
strcpy(string,"This is a very long string that requires more \ than one line");Note that if you indent the second line, the spaces will be part of the string.
The compiler concatenates adjacent string constants. Therefore, you can also span a string constant over one line as shown:
strcpy(string, "This is a very long string that requires more " "than one line");When you indent the second line with this method, the spaces are not part of the string.
The type of a string is array of char, and strings obey the same conversion rules as other arrays. Except when a string appears as the operand of sizeof or as an initializer, it is converted to a pointer to the first element of the string. Note also that the null string,
""is legal, and contains a single trailing null character.
The following shows how a program can be organized:
/* preprocessor directives */
#include <stdio.h>
#define WEIGHTING_FACTOR 0.6
/* global typedef declaration */
typedef float THIRTY_TWO_BIT_REAL;
/* global variable declaration */
THIRTY_TWO_BIT_REAL correction_factor = 1.15;
/* prototype */
float average (float arg1, THIRTY_TWO_BIT_REAL arg2)
/* start of function body */
{
/* local variable declaration */
float mean;
/* assignment statement */
mean = (arg1 * WEIGHTING_FACTOR) +
(arg2 * (1.0 - WEIGHTING_FACTOR));
/* return statement */
return (mean * correction_factor);
/* end of function body */
}
int main(void)
/* start of function body */
{
/* local variable declarations */
float value1, value2, result;
/* statements */
printf("Enter two values -- ");
scanf("%f%f", &value1, &value2);
result = average(value1, value2);
/* continuation line */
printf("The weighted average using a correction \
factor of %4.2f is %5.2f\n", correction_factor, result);
/* end of function body */
}