Arrays are declared with the bracket punctuators [ ], as shown in the following syntax:
storage-class-specifier(opt) type-specifier declarator [constant-expression-list(opt)]
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 an integral constant expression with a value greater than zero. Omitting the constant expression creates an incomplete array declaration, which is useful in the following cases:
extern int array1[]; int first_function(void) { . . . }
In a separate compilation unit:
int array1[10]; int second_function(void) { . . . }
The array size specifier may only be omitted from the first pair of brackets in a multidimensional array declaration. This is because an array's elements must have complete types, even if the array itself has an incomplete type.
char array_one[] = "Shemps"; char array_two[] = { 'S', 'h', 'e', 'm', 'p', 's', '\0' };
The two definitions initialize variables with identical elements. These arrays have seven elements: six characters and the null character (\0), which terminates all character strings. The size of the array is determined from the number of characters in the initializing character-string constant or initialization list. Initializing an incomplete array completes the array type. An array is completed at the end of its initializer list.
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; }
After the function adder
is called, parameter
param_string
receives the address of the first
character of argument arg_str
, which can then be
accessed in adder
. The declaration of param_
string
serves only to give the type of the parameter, not
to reserve storage for it.
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.
char c[4] = "abcd";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.