United States    
COMPAQ STORE | PRODUCTS |
SERVICES | SUPPORT | CONTACT US | SEARCH
Compaq C

Compaq C
Language Reference Manual


Previous Contents Index

4.7.2 Pointers and Arrays

Data objects in an array can be referenced through pointers instead of using array subscripts. The data type of such a pointer is referred to as "pointer to array of type". The array name itself behaves like a pointer, so there are several alternative methods to accessing array elements. For example:


int x[5] = { 0, 1, 2, 3, 4 }; /* Array x declared with five elements       */ 
int *p = x;                   /* Pointer declared and initialized to point */ 
                              /* to the first element of the array x       */ 
int a, b; 
a = *(x + 3);                 /* Pointer x incremented by twelve bytes     */ 
                              /* to reference element 3 of x               */ 
b = x[3];                     /* b now holds the same value as a           */ 

In the previous example, a receives the value 3 by using the dereferencing operator (*). b receives the same value by using the subscripting operator. See Chapter 6 for more information on the different unary operators.

Note that the assignment of a was a result of incrementing the pointer to x . This principle, known as scaling, applies to all types of pointer arithmetic. In scaling, the compiler considers the size of an array element when calculating memory addresses of array members. For example, each member of the array x is 4 bytes long, and adding three to the initial pointer value automatically converts that addition to 3 * (the size of the array member, which in this case is 4). Therefore, the intuitive meaning of z = *(y + 3); is preserved.

When passing arrays as function arguments, only a pointer to the first element of the array is passed to the called function. The conversion from array type to pointer type is implicit. Once the array name is converted to a pointer to the first element of the array, you can increment, decrement, or dereference the pointer, just like any other pointer, to manipulate data in the array. For example:


int func(int *x, int *y) /* The arrays are converted to pointers         */ 
{ 
   *y = *(x + 4);        /* Various elements of the arrays are accessed  */ 
} 

Remember that a pointer is large enough to hold only an address; a pointer into an array holds the address of an element of that array. The array itself is large enough to hold all members of the array.

When applied to arrays, the sizeof operator returns the size of the entire array, not just the size of the first element in the array.

4.7.3 Variable-Length Arrays

Variable-length arrays allow array objects with auto storage class, and array typedef s declared at block scope, to have bounds that are runtime-computed expressions.

Variable-length arrays also allow the declaration and definition of functions whose parameters are arrays dimensioned by other parameters (similar to Fortran assumed-shape arrays).

The following example illustrates both uses. Note that the definition of function sub uses prototype syntax and that the dimension parameters precede the array parameter that uses them. In order to define a function with the dimension parameters following the array parameter that uses them, the function definition must be written using using Kernighan and Ritchie C syntax (because that syntax allows the declarations of the types of the parameters to be written in a different order from the parameters themselves). Kernighan and Ritchie function definitions should generally be avoided.


  #include <stdio.h> 
  #include <stdlib.h> 
 
  void sub(int, int, int[*][*]); 
 
  int main(int argc, char **argv) 
  { 
      if (argc != 3) { 
          printf("Specify two array bound arguments.\n"); 
          exit(EXIT_FAILURE); 
      } 
      { 
          int dim1 = atoi(argv[1]); 
          int dim2 = atoi(argv[2]); 
          int a[dim1][dim2]; 
          int i, j, k = 0; 
          for (i = 0; i &lt; dim1; i++) { 
              for (j = 0; j &lt; dim2; j++) { 
                  a[i][j] = k++; 
              } 
          } 
          printf("dim1 = %d, dim2 = %d.", 
                 sizeof(a)/sizeof(a[0]), 
                 sizeof(a[0])/sizeof(int)); 
 
          sub(dim1, dim2, a); 
          sub(dim2, dim1, a); 
      } 
      exit(EXIT_SUCCESS); 
  } 
 
  void sub(int sub1, int sub2, int suba[sub1][sub2]) 
  { 
      int i, j, k = 0; 
      printf("\nIn sub, sub1 = %d, sub2 = %d.", 
                        sub1,      sub2); 
      for (i = 0; i &lt; sub1; i++) { 
          printf("\n"); 
          for (j = 0; j &lt; sub2; j++) { 
              printf("%4d", suba[i][j]); 
          } 
      } 
  } 

On OpenVMS systems, variable-length arrays can often be used in place of the non-standard alloca intrinsic, __alloca .

However, an important difference between __alloca and variable-length arrays is that the storage allocated by __alloca is not freed until return from the function, while the storage allocated for a variable-length array is freed on exit from the block in which it is allocated. If __alloca is called within the scope of a variable-length array declaration (including within a block nested within the block containing a variable-length array declaration), then the storage allocated by that call to __alloca is freed at the same time that the storage for the variable-length array is freed (that is, at block exit rather than at function return). The compiler issues a warning in such cases.

4.8 Declaring Structures and Unions

A structure consists of a list of members whose storage is allocated in an ordered sequence. A union consists of a sequence of members whose storage overlaps. Structure and union declarations have the same form, as follows:

struct-or-union-specifier:

struct-or-union identifieropt { 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-listopt
type-qualifier specifier-qualifier-list opt

struct-declarator-list:

struct-declarator
struct-declarator-list , struct-declarator

struct-declarator:

declarator
declaratoropt : constant-expression

Neither a structure nor union member can have a function type or an incomplete type. Structures and unions cannot contain instances of themselves as members, but they can have pointers to instances of themselves as members. The declaration of a structure with no members is accepted; its size is zero.

Each structure or union definition creates a unique structure or union type within the compilation unit. The struct or union keywords can be followed by a tag, which gives a name to the structure or union type in much the same way that an enum tag gives a name to an enumerated type. The tag can then be used with the struct or union keywords to declare variables of that type without repeating a long definition.

The tag is followed by braces { } that enclose a list of member declarations. Each declaration in the list gives the data type and name of one or more members. The names of structure or union members can be the same as other variables, function names, or members in other structures or unions; the compiler distinguishes them by context. In addition, the scope of the member name is the same as the scope of the structure or union in which it appears. The structure or union type is completed when the closing brace completes the list.

An identifier used for a structure or union tag must be unique among the visible tags in its scope, but the tag identifier can be the same as an identifier used for a variable or function name. Tags can also have the same spellings as member names; the compiler distinguishes them by name space and context. The scope of a tag is the same as the scope of the declaration in which it appears.

Structures and unions can contain other structures and unions. For example:


struct person 
   { 
      char first[20]; 
      char middle[3]; 
      char last[30]; 
      struct         /*  Nested structure here  */ 
      { 
          int day; 
          int month; 
          int year; 
      } birth_date; 
   }  employees, managers; 

Structure or union declarations can take one of the following forms:

4.8.1 Similarities Between Structures and Unions

Structures and unions share the following characteristics:

4.8.2 Differences Between Structures and Unions

The difference between structures and unions lies in the way their members are stored and initialized, as follows:

4.8.3 Bit Fields

One of the advantages of structures is the ability to pack data into them bit-by-bit.

A structure member often is an object with a basic type size. However, you can also declare a structure member that is composed only of a specified number of bits. Such a member is called a bit field; its length, an integral nonnegative constant expression, is set off from the field name by a colon, as shown by the following syntax:

struct-declarator:

declarator: constant-expression
:constant-expression

Bit fields provide greater control over the structure's storage allocation and allow tighter packing of information in memory. By using bit fields, data can be densely packed into storage.

A bit field's type must be specified (except with unnamed bit fields), and a bit field can have the int , unsigned int , or signed int type. The bit field's value must be small enough to store in an object of the declared size.

In the compiler's default mode, the enum , long , short , and char types are also allowed for bit fields.

A bit field can be named or unnamed. A bit-field declaration without a declarator (for example, :10 ) indicates an unnamed bit field, which is useful for padding a structure to conform to a specified layout. If the bit field is assigned a width of 0, it indicates that no further bit fields should be placed in the alignment unit, and it cannot name a declarator. Use a colon (:) to separate the member's declarator (if any) from a constant expression that gives the field width in bits. No field can be longer than 32 bits (1 longword).

Since nonbit-field structure members are aligned on at least byte boundaries, the unnamed form can create unnamed gaps in the structure's storage. As a special case, an unnamed field of width 0 causes the next member (normally another field) to be aligned on at least a byte boundary; that is, a bit-field structure member with zero width indicates that no further bit field should be packed into an alignment unit.

The following restrictions apply to the use of bit fields:

Sequences of bit fields are packed as tightly as possible. In C, bit fields are assigned from right to left; that is, from low-order to high-order bit.

To create bit fields, specify an identifier, a colon, and the identifier's width (in bits) as a structure member. In the following example, three bit fields are created in the structure declaration:


struct { 
 unsigned int a : 1;  /*  Named bit field (a)    */ 
 unsigned int   : 0;  /*  Unnamed bit field = 0  */ 
 unsigned int   : 1;  /*  Unnamed bit field      */ 
}  class; 

The first and third bit fields are one bit wide, the second is zero bits wide, which forces the next member to be aligned on a natural or byte boundary.

Bit fields (including zero-length bit fields) not immediately declared after other bit fields have the alignment requirement imposed by their type, but never a lesser alignment requirement than that of int . In a declaration of a bit field that immediately follows another bit field, the bits are packed into adjacent space in the same alignment unit, if sufficient space remains; otherwise, padding is inserted and the second bit field is put into the next alignment unit.

See your Compaq C documentation for platform-specific information on bit-field alignment within a structure.

4.8.4 Initializing Structures

All structures can be initialized with a brace-enclosed list of component initializers. Structures with automatic storage class can also be initialized by an expression of compatible type.

Initializers are assigned to components on a one-to-one basis. If there are fewer initializers than members for a structure, the remaining members are initialized to 0. Listing too many initializers for the number of components in a structure is an error. All unnamed structure or union members are ignored during initialization.

Separate initializing values with commas and delimit them with braces { }. The following example initializes two structures, each with two members:


struct 
   { 
      int i; 
      float c; 
   }  a = { 1, 3.0e10 },  b = { 2, 1.5e5 }; 

The compiler assigns structure initializers in increasing member order. Note that there is no way to initialize a member in the middle of a structure without also initializing the previous members. Example 4-1 shows the initialization rules applied to an array of structures.

Example 4-1 The Rules for Initializing Structures

#include <stdio.h> 
 
main() 
{ 
   int m, n; 
   static struct 
      { 
         char ch; 
         int i; 
         float c; 
      }  ar[2][3] = 
(1)         { 
(2)            { 
(3)               { 'a', 1, 3e10 }, 
               { 'b', 2, 4e10 }, 
               { 'c', 3, 5e10 }, 
            } 
         }; 
 
   printf("row/col\t ch\t i\t      c\n"); 
   printf("-------------------------------------\n"); 
   for (n = 0; n < 2; n++) 
      for (m = 0; m < 3; m++) 
         { 
            printf("[%d][%d]:", n, m); 
            printf("\t %c \t %d \t %e \n", 
                   ar[n][m].ch, ar[n][m].i, ar[n][m].c); 
         } 
} 

Key to Example 4-1:


Previous Next Contents Index
  

1.800.AT.COMPAQ

privacy and legal statement