|
|
HP C
|
Previous | Contents | Index |
A void pointer is a pointer without a specified data type to describe the object to which it points. In effect, it is a generic pointer. (Before the ANSI C standard, char * was used to define generic pointers; this practice is now discouraged by the ANSI standard because it is less portable.)
A pointer to any type can be assigned to a void pointer without a cast, and vice versa. See Section 6.4.6 for more information on the cast operation. The following statements show how a void pointer can be assigned to other typed pointers, without explicit casts:
float *float_pointer; void *void_pointer; . . . float_pointer = void_pointer; /* or, */ void_pointer = float_pointer; |
A void pointer is often used in function calls, function arguments, or function prototypes when a parameter or return value is a pointer of an unknown type. Consider the following example, where a void pointer is used as a generic return value:
void *memcpy (void *s1, const void *s2, size_t n); { void *generic_pointer; . . . /* The function return value can be a pointer to many types. */ generic_pointer = func_returning_pointer( arg1, arg2, arg3 ); . . . /* size_t is a defined type */ } |
See Section 5.3 for further information about using void in function declarations.
The pointer object can be initialized with a single expression. For example:
int i = 10; int *p = &i; /* p is a pointer to int, initialized */ /* as holding the address of i */ |
Without an initializer, the values of static and extern pointers are automatically initialized to null pointers (pointers to memory location 0).
The following declaration defines p with type pointer to char , and initializes p to point to an object of type array of char with length 4, whose elements are initialized by a character string literal. (The null character is the fourth member of the array.) If an attempt is made to use p to modify the contents of the array, the behavior is undefined.
char *p = "abc"; |
Arrays are declared with the bracket punctuators [ ], as shown in the following syntax:
storage-class-specifieropt type-specifier declarator [* or constant-expression-listopt] |
The following example shows a declaration of a 10-element array of integers, a variable called table_one :
int table_one[10]; |
The type-specifier shows the data type of the elements. The elements of an array can be of any scalar or aggregate data type. The identifier table_one specifies the name of the array. The constant expression 10 gives the number of elements in a single dimension. Arrays in C are zero-based; that is, the first element of the array is identified with a 0 subscript, such as the one shown in the following example:
int x[5]; x[0] = 25; /* The first array element is assigned the value 25 */ |
The expression between the brackets in the declaration must be either the ( * ) punctuator or an integral constant expression with a value greater than zero.
If * is specified between the brackets, then the array type is a variable-length array type of unspecified size, which can be used only in declarations with function prototype scope.
If the size expression is an integer constant expression and the element type has a known constant size, the array type is not a variable-length array type. Otherwise, it is a variable-length array type. The size of each instance of a variable-length array type does not change during its lifetime. For more information on variable-length arrays, see Section 4.7.3.
Omitting the * or the constant expression creates an incomplete array declaration, which is useful in the following cases:
extern int array1[]; int first_function(void) { . . . } |
int array1[10]; int second_function(void) { . . . } |
char array_one[] = "Shemps"; char array_two[] = { 'S', 'h', 'e', 'm', 'p', 's', '\0' }; |
main() { /* Initialize array */ static char arg_str[] = "Thomas"; int sum; sum = adder(arg_str); /* Pass address of first array element */ . . . } |
/* adder adds ASCII values of letters in array */ int adder( char param_string[]) { int i, sum = 0; /* Incrementer and sum */ /* Loop until NULL char */ for (i = 0; param_string[i] != '\0'; i++) sum += param_string[i]; return sum; } |
Array members can also be pointers. The following example declares an array of floating-point numbers and an array of pointers to floating-point numbers:
float fa[11], *afp[17]; |
When a function parameter is declared as an array, the compiler treats the declaration as a pointer to the first element of the array. For example, if x is a parameter and is intended to represent an array of integers, it can be declared as any one of the following declarations:
int x[]; int *x; int x[10]; |
Note that the specified size of the array does not matter in the case of a function parameter, since the pointer always points to only the first element of the array.
C supports arrays declared as an array of arrays. These are sometimes called multidimensional arrays. Consider the following example, where variable table_one is a two-dimensional array containing 20 integers:
int table_one[10][2]; |
Arrays are stored in row-major order, which means the element table_one[0][0] (in the previous example) immediately precedes table_one[0][1] , which in turn immediately precedes table_one[1][0] .
Arrays are initialized with a brace-enclosed list of constant expressions. A list of initializers for an incomplete array declaration completes the array's type and completely defines the array size. Therefore, when initializing an array of unknown size, the number of initializers in the initializer list determines the size of the array. For example, the following declaration initializes an array of three elements:
int x[] = { 1, 2, 3 }; |
If the array being initialized has a storage class of static , the initializers must be constant expressions.
Initializers for an array of a given size are assigned to array members on a one-to-one basis. If there are too few initializers for all members, the remaining members are initialized to 0. Listing too many initializers for a given size array is an error. For example:
int x[5] = { 0, 1, 2, 3, 4, 5 }; /* error */ |
String literals are often assigned to a char or wchar_t array. In this case, each character of the string represents one member of a one-dimensional array, and the array is terminated with the null character. When an array is initialized by a pointer to a string literal, the string literal cannot be modified through the pointer.
When initializing an array with a string literal, use quotation marks around the initializing string. For example:
char string[26] = { "This is a string literal." }; /* The braces above are optional here */ |
The terminating null character is appended to the end of the string if the size permits, as it does in this case. Another form for initializing an array with characters is the following:
char string[12] = {'T', 'h', 'i', 's', ' ', 'w', 'a', 'y' }; |
The preceding example creates a one-dimensional array containing the string value " This way ". The characters in this array can be freely modified. Remaining uninitialized array members will be automatically initialized to zero.
If the size of the array used for a string literal is not explicitly stated, its size is determined by the number of characters in the string (including the terminating null character). If the size of the array is explicitly stated, initializing the array with a string literal longer than the array is an error.
There is one special case where the null character is not automatically appended to the array. This case is when the array size is explicitly specified and the number of initializers completely fills the array size. For example:
Here, the array c holds only the four specified characters, a , b , c , and d . No null character terminates the array. |
Using the following rules, you can omit braces when initializing the members of a multidimensional arrays:
Consider the following example:
float x[4][2] = { { 1, 2 } { 3, 4 } { 5, 6 } }; |
In this example, 1 and 2 initialize the first row of the array x , and the following two lines initialize the second and third rows, respectively. The initialization ends before the fourth row is initialized, so the members of the fourth row default to 0. Here is the result:
x[0][0] = 1; x[0][1] = 2; x[1][0] = 3; x[1][1] = 4; x[2][0] = 5; x[2][1] = 6; x[3][0] = 0; x[3][1] = 0; |
The following declaration achieves the same result:
float x[4][2] = { 1, 2, 3, 4, 5, 6 }; |
Here, the compiler fills the array row by row with the available initial values. The compiler places 1 and 2 in the first row ( x[0] ), 3 and 4 in the second row ( x[1] ), and 5 and 6 in the third row ( x[2] ). The remaining members of the array are initialized to zero.
|
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.
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 < dim1; i++) { for (j = 0; j < 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 < sub1; i++) { printf("\n"); for (j = 0; j < 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.
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:
struct |
struct-declaration-list:
struct-declaration |
struct-declaration:
specifier-qualifier-list struct-declarator-list ; |
specifier-qualifier-list:
type-specifier specifier-qualifier-listopt |
struct-declarator-list:
struct-declarator |
struct-declarator:
declarator |
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:
struct person { char first[20]; char middle[3]; char last[30]; }; struct person employee; /* The tag (person) identifies employee as */ a structure with members shown in */ the declaration of person */ |
Previous | Next | Contents | Index |
|