A C array is a fixed-size sequence of elements of one type stored contiguously. Its size is part of the array type at the declaration site, valid indexes run from zero through count minus one, and C performs no automatic bounds check. Reading or writing outside the array is undefined behavior even when the program appears to work.
This lesson connects storage, initialization, traversal, pointer decay, function parameters, and multidimensional layout. You will learn where sizeof can recover an element count, where that information is lost, and how to carry lengths explicitly through program boundaries.
The element count in int values[5] reserves space for five int objects. An initializer can provide every value, infer the count, or initialize the remaining elements to zero. Automatic arrays without an initializer contain indeterminate values and must be assigned before they are read.
Array length is normally fixed for its lifetime. A variable length array, where supported by the chosen C implementation and language mode, uses a runtime bound at block scope; it is not a resizable container. For portable APIs and large dynamic sizes, allocate deliberately and keep the count with the pointer.
// Declaration
int numbers[5];
// Declaration with initialization
int numbers[5] = {10, 20, 30, 40, 50};
// Size inferred from initializer
int numbers[] = {10, 20, 30, 40, 50};
// Access element
printf("%d", numbers[0]); // 10
numbers[2] = 99; // modify element
A loop over count elements uses index < count, not index <= count. Prefer size_t for counts and indexes derived from sizeof because sizeof returns size_t. Check an external index before using it, and be careful when counting downward with an unsigned type because it cannot represent a negative stop value.
The expression sizeof values / sizeof values[0] computes the element count only where values is still an array. It works in the same scope as the declaration, but not after the array has adjusted to a pointer parameter.
#include <stdio.h>
int main(void) {
int values[] = {4, 7, 1, 9};
size_t count = sizeof values / sizeof values[0];
int total = 0;
for (size_t i = 0; i < count; ++i) {
total += values[i];
}
printf("count=%zu total=%d\n", count, total);
return 0;
}
count=4 total=21
In most expressions, an array expression converts to a pointer to its first element. A function parameter written int values[] is adjusted to int *values, so the function does not receive the array length and sizeof values measures a pointer. Pass the count as a separate parameter and document whether null is allowed when count is zero.
The pointer points to existing elements; it does not own or copy the array. The caller must keep the array alive for the duration of the call. Add const to the pointed-to element type when the function only reads values.
#include <stddef.h>
int sum(const int values[], size_t count) {
int total = 0;
for (size_t i = 0; i < count; ++i) {
total += values[i];
}
return total;
}
const prevents the function from modifying elements through values. count supplies the boundary that the adjusted pointer does not carry.
A declaration such as int grid[2][3] is an array of two elements, where each element is an array of three int values. C stores the rows contiguously in row-major order. grid[row][column] first selects a row array and then an element inside that row.
When passing a multidimensional array, later dimensions must be known so pointer arithmetic can locate each row. A parameter such as int grid[][3] adjusts to a pointer to an array of three int values. Pass the row count separately.
// Declaration: rows x columns
int matrix[3][4];
// Initialization
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// Access: matrix[row][col]
printf("%d", matrix[1][2]); // 6
#include <stdio.h>
int main() {
int scores[] = {85, 92, 78, 95, 88};
int n = sizeof(scores) / sizeof(scores[0]); // number of elements = 5
// Traverse and print
printf("Scores: ");
for (int i = 0; i < n; i++) {
printf("%d ", scores[i]);
}
printf("\n");
// Find sum and average
int sum = 0;
for (int i = 0; i < n; i++) {
sum += scores[i];
}
printf("Sum: %d\n", sum);
printf("Average: %.2f\n", (float)sum / n);
// Find maximum
int max = scores[0];
for (int i = 1; i < n; i++) {
if (scores[i] > max) max = scores[i];
}
printf("Max: %d\n", max);
return 0;
}
/*
Scores: 85 92 78 95 88
Sum: 438
Average: 87.60
Max: 95
*/
#include <stdio.h>
int main() {
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int b[2][3] = {{7, 8, 9}, {10, 11, 12}};
int c[2][3];
// Matrix addition
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
c[i][j] = a[i][j] + b[i][j];
}
}
// Print result matrix
printf("Matrix A + B:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%4d", c[i][j]);
}
printf("\n");
}
return 0;
}
/*
Matrix A + B:
8 10 12
14 16 18
*/
#include <stdio.h>
// Arrays are always passed by reference (pointer to first element)
int findMax(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Passing 2D array to function
void print2D(int rows, int cols, int arr[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d", arr[i][j]);
}
printf("\n");
}
}
int main() {
int nums[] = {34, 12, 67, 45, 89, 23};
int n = sizeof(nums) / sizeof(nums[0]);
printf("Array: ");
printArray(nums, n);
printf("Max element: %d\n", findMax(nums, n));
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("\n2D Array:\n");
print2D(2, 3, matrix);
return 0;
}
/*
Array: 34 12 67 45 89 23
Max element: 89
2D Array:
1 2 3
4 5 6
*/
Arrays are not assignable as whole objects. Copy elements with a loop or use memcpy only when bytewise copying is valid for the element type and source and destination do not overlap. Use memmove when byte ranges may overlap.
A local automatic array stops existing when its block ends, so returning a pointer to it creates a dangling pointer. Return data through caller-provided storage, allocate with a documented ownership rule, or wrap the array inside a structure that can be returned by value.
Try this next
0 of 3 completed
No. An array is an object containing elements; it converts to a pointer in many expressions, but sizeof and address-of reveal the distinction.
No. Track dynamic storage with a pointer, current count, and capacity when resizing is required.
The compiler needs each row size to calculate the address produced by pointer arithmetic.
Explore 500+ free tutorials across 20+ languages and frameworks.