C Language Series #32: Array Initialization in C
Arrays are fundamental data structures in C, allowing you to store a collection of elements of the same data type. While declaring an array reserves memory for it, populating that memory with meaningful values is where array initialization comes in. Proper initialization is crucial for preventing undefined behavior and ensuring your programs work as expected. This post will delve into the various ways to initialize arrays in C, from basic declarations to more advanced techniques.
The Basics: Initializing at Declaration
The most common way to initialize an array is at the time of its declaration. You provide a list of values,
enclosed in curly braces {}, separated by commas. These values are assigned to the array elements
in order, starting from the first element (index 0).
The general syntax looks like this:
type arrayName[size] = {value1, value2, value3, ...};
Here are some examples with different data types:
#include <stdio.h>
int main() {
// Integer array initialization
int scores[5] = {90, 85, 92, 78, 95};
// Float array initialization
float prices[3] = {10.99f, 25.50f, 5.00f};
// Character array initialization
char grades[4] = {'A', 'B', 'C', 'D'};
printf("Scores: %d, %d, %d, %d, %d\n", scores[0], scores[1], scores[2], scores[3], scores[4]);
printf("Prices: %.2f, %.2f, %.2f\n", prices[0], prices[1], prices[2]);
printf("Grades: %c, %c, %c, %c\n", grades[0], grades[1], grades[2], grades[3]);
return 0;
}
In these examples, the number of initializers exactly matches the size of the array.
Partial Initialization: When You Provide Fewer Values
What happens if you provide fewer initializers than the declared size of the array? C handles this gracefully:
the elements for which no explicit initializer is provided will be automatically initialized to zero.
This applies to numeric types (int, float, double),
character types (char, which becomes \0), and pointers (which become NULL).
#include <stdio.h>
int main() {
int numbers[7] = {10, 20, 30}; // Size is 7, only 3 values provided
printf("Partial Initialization Example:\n");
for (int i = 0; i < 7; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
// Output:
// numbers[0] = 10
// numbers[1] = 20
// numbers[2] = 30
// numbers[3] = 0
// numbers[4] = 0
// numbers[5] = 0
// numbers[6] = 0
return 0;
}
This behavior is very useful, especially when you want to ensure all elements start at a known default value.
Letting the Compiler Determine Array Size
If you initialize an array at declaration, you can actually omit the size of the array. The compiler will automatically determine the size based on the number of initializers provided. This is particularly convenient when you have a long list of initial values and don't want to manually count them.
#include <stdio.h>
int main() {
int dynamic_array[] = {100, 200, 300, 400, 500, 600}; // Compiler infers size as 6
printf("Compiler-Inferred Size Example:\n");
printf("Size of dynamic_array: %zu bytes\n", sizeof(dynamic_array));
printf("Number of elements: %zu\n", sizeof(dynamic_array) / sizeof(dynamic_array[0]));
for (int i = 0; i < (sizeof(dynamic_array) / sizeof(dynamic_array[0])); i++) {
printf("dynamic_array[%d] = %d\n", i, dynamic_array[i]);
}
return 0;
}
This technique is commonly used for defining constant lookup tables or strings.
Designated Initializers (C99 and Later)
C99 introduced designated initializers, which allow you to initialize specific elements of an array by their index, without having to list all preceding elements. This is incredibly useful for sparse arrays or when you want to explicitly set certain elements and leave others to their default (zero) value.
The syntax uses [index] = value.
#include <stdio.h>
int main() {
int sparse_array[10] = {[0] = 10, [5] = 50, [9] = 90};
printf("Designated Initializers Example:\n");
for (int i = 0; i < 10; i++) {
printf("sparse_array[%d] = %d\n", i, sparse_array[i]);
}
// Output:
// sparse_array[0] = 10
// sparse_array[1] = 0
// sparse_array[2] = 0
// sparse_array[3] = 0
// sparse_array[4] = 0
// sparse_array[5] = 50
// sparse_array[6] = 0
// sparse_array[7] = 0
// sparse_array[8] = 0
// sparse_array[9] = 90
// You can also mix designated and sequential initializers
int mixed_array[5] = {1, 2, [4] = 5}; // 1 at index 0, 2 at index 1, 5 at index 4.
// Indices 2 and 3 will be 0.
printf("\nMixed Initializers Example:\n");
for (int i = 0; i < 5; i++) {
printf("mixed_array[%d] = %d\n", i, mixed_array[i]);
}
return 0;
}
Initializing Character Arrays and Strings
Character arrays are frequently used to store strings in C. There are specific ways to initialize them:
-
Using a string literal: This is the most common and convenient method. When you use a
string literal (e.g.,
"Hello"), the compiler automatically adds a null terminator (\0) at the end of the character sequence.char name[] = "Alice"; // Compiler infers size as 6 ('A','l','i','c','e','\0') char message[10] = "Hi there!"; // Size 10, 'Hi there!' + '\0' + remaining 0s -
Using an explicit list of characters: You can also list characters one by one.
In this case, you must remember to include the null terminator
\0yourself if you intend to treat it as a C-style string.char manual_name[6] = {'B', 'o', 'b', '\0'}; // Explicit null terminator. Size 6. char short_name[3] = {'X', 'Y', 'Z'}; // NOT a C-string! No null terminator. // Printing this with %s would lead to undefined behavior.
Let's see an example contrasting these:
#include <stdio.h.C> // Typo in prompt, fixed to stdio.h
int main() {
char str_literal[] = "Hello"; // Compiler adds \0, size 6
char char_list[6] = {'W', 'o', 'r', 'l', 'd', '\0'}; // Manual \0, size 6
char no_null[5] = {'A', 'B', 'C', 'D', 'E'}; // No \0, NOT a C-string
printf("String literal: %s (Size: %zu)\n", str_literal, sizeof(str_literal));
printf("Char list: %s (Size: %zu)\n", char_list, sizeof(char_list));
// WARNING: Printing no_null with %s is DANGEROUS and causes undefined behavior
// because printf("%s") expects a null terminator to know where the string ends.
// printf("No null string: %s (Size: %zu)\n", no_null, sizeof(no_null));
// Instead, print characters individually:
printf("No null array content: ");
for (int i = 0; i < sizeof(no_null); i++) {
printf("%c", no_null[i]);
}
printf(" (Size: %zu)\n", sizeof(no_null));
return 0;
}
Always remember the null terminator (\0) when dealing with C strings, whether
explicitly or implicitly via string literals.
Initializing Multi-dimensional Arrays
Multi-dimensional arrays, like 2D arrays (matrices), can be initialized using nested curly braces. Each inner pair of braces represents a row (or sub-array).
#include <stdio.h>
int main() {
// 2x3 matrix initialization
int matrix[2][3] = {
{1, 2, 3}, // Row 0
{4, 5, 6} // Row 1
};
printf("2D Array Initialization:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// Partial initialization of multi-dimensional arrays also defaults to zero
int partial_matrix[2][3] = {
{10}, // Row 0: {10, 0, 0}
{20, 30} // Row 1: {20, 30, 0}
};
printf("\nPartial 2D Array Initialization:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", partial_matrix[i][j]);
}
printf("\n");
}
// Omitting the first dimension's size is allowed, compiler infers it
int inferred_matrix[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}; // Compiler infers 3 rows.
printf("\nInferred 2D Array Initialization:\n");
for (int i = 0; i < (sizeof(inferred_matrix) / sizeof(inferred_matrix[0])); i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", inferred_matrix[i][j]);
}
printf("\n");
}
return 0;
}
Note that you must specify all dimensions except possibly the very first one when declaring multi-dimensional arrays. The compiler needs to know the size of each "row" to correctly calculate memory offsets.
Key Considerations and Best Practices
-
Local vs. Global/Static Arrays:
- Local arrays (declared inside a function without
static) are not automatically initialized. Their contents are garbage values unless you explicitly initialize them. Accessing uninitialized local array elements leads to undefined behavior. - Global arrays (declared outside any function) and static arrays
(declared inside a function with
static) are automatically initialized to zero (orNULLfor pointers,\0for characters) if no explicit initializer is provided.
- Local arrays (declared inside a function without
- Undefined Behavior: Accessing array elements that have not been initialized (especially local arrays) can lead to unpredictable program behavior, crashes, or security vulnerabilities. Always initialize your arrays.
-
The Null Terminator (
\0): For character arrays intended to be used as C-style strings, ensure they are null-terminated. String literals handle this automatically; manual character lists require explicit inclusion of\0. - Matching Size and Initializers: While partial initialization fills remaining elements with zero, providing more initializers than the declared size of the array is a compile-time error.
Mastering array initialization is a crucial step in writing robust and reliable C programs. By understanding these different methods, you can effectively manage memory and data for various programming scenarios.