Arrays and Strings in C
October 8, 2026 • 5 min read

Table of contents
Part of the series:C Explained
So far every variable held exactly one value. Real programs deal in lists: grades, names, coordinates. C’s built-in structures for a sequence of the same type are arrays, and a string is just an array of characters. This is the first of two posts on the data structures the language gives you ready-made; structs, unions, and enums are the second.
Arrays
An array is a contiguous run of values of one type, each with an index. Declare it with the element type, the name, and the size in square brackets:
int scores[5]; // 5 ints, indices 0..4
Indexing starts at zero, so the first element is scores[0] and the last is scores[4]:
scores[0] = 92;
scores[1] = 85;
scores[2] = 88;
printf("%d\n", scores[2]); // 88
You usually initialize at declaration and skip the size, letting the compiler count:
int scores[] = {92, 85, 88, 70, 91};
The size is the number of elements, not the byte size. sizeof(scores) gives total bytes; divide by the element size to get the count:
int n = sizeof(scores) / sizeof(scores[0]); // 5
Iterating
The for loop from the loops post is the standard companion; index variables and arrays were made for each other:
int total = 0;
for (int i = 0; i < 5; i++) {
total += scores[i];
}
printf("average: %d\n", total / 5);
A common beginner error is looping one too far and reading past the last element. That is legal to write but undefined behavior: C does not check array bounds. scores[5] is a memory error, not an exception. There are no error messages, just garbage values or a crash, and compilers warn about obvious cases only with extra flags. The array size is your responsibility, so the sizeof-based count above is worth keeping in a variable.
Multidimensional arrays
An array of arrays is written with two sizes. int grid[3][4] means “3 rows, 4 columns”:
int grid[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 1, 2, 3},
};
printf("%d\n", grid[1][2]); // row 1, column 2 -> 7
The nested loops from the loops post walk rows and columns. Underneath it is still one contiguous block of memory, laid out row by row.
Strings
A string in C has no type of its own. It is a char array whose characters end with a null terminator: the byte '\0' (value zero). Every string function reads until it finds that byte, so the rule is: the buffer holds the visible characters plus one for '\0'.
char name[] = "Ada"; // actually 4 chars: 'A','d','a','\0'
sizeof(name) is 4, even though “Ada” has three letters. A string literal like "hello" is stored by the compiler as h e l l o \0. When you declare an array large enough, you can copy a string into it:
char buffer[32];
printf("What is your name? ");
fgets(buffer, sizeof(buffer), stdin);
printf("Hello, %s\n", buffer);
fgets(buffer, size, stdin) reads a line and respects the buffer size, stopping at the newline or filling the buffer and always null-terminating. Prefer it over scanf("%s", ...), which reads without a size limit and overflows any buffer you hand it. fgets leaves the trailing newline in the buffer, so you will often strip it:
buffer[strcspn(buffer, "\n")] = '\0';
strcspn (from string.h) returns the index of the first newline, and this line replaces it with the terminator.
The string.h toolbox
The <string.h> header provides the string functions. The ones to know first, with their footguns:
strlen(s)- returns the number of characters up to\0(the “length”, not counting the terminator).strcpy(dst, src)- copiessrcintodst, no size check: classic overflow source. Prefer the boundedstrncpy(dst, src, size)(and remember to ensuredstends in'\0'yourself) orsnprintf.strcat(dst, src)- appendssrctodst; same overflow risk. Build withsnprintfinstead when safety matters.strcmp(a, b)- compares and returns0if equal, negative/positive by order. Use==on strings and you are comparing addresses, not text (an easy trap; addresses become first-class in the pointers post).strchr(s, c)- finds the first occurrence of a character, returning a pointer orNULL.
#include <string.h>
char combined[64];
snprintf(combined, sizeof(combined), "%s-%s", "Ada", "Lovelace");
printf("%s\n", combined); // Ada-Lovelace
printf("len: %zu\n", strlen(combined)); // 12
snprintf(dst, size, format, ...) is the safe workhorse: it writes at most size characters including the terminator. Prefer it anytime you build text.
The array-to-parameter surprise
When you pass an array to a function, it does not arrive as a copy. Arrays are passed as a pointer to their first element, and inside the function sizeof no longer gives the array size. Functions that take an array therefore also take its length:
int sum(int arr[], int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += arr[i];
}
return total;
}
int main(void) {
int scores[] = {92, 85, 88};
printf("%d\n", sum(scores, 3)); // 265
return 0;
}
The full story behind “arrays decay to pointers” is one of the main events of the pointers post. For now, remember the two rules stated above: pass the length, and do not trust sizeof on a parameter.
The big picture
Arrays are contiguous runs of one type, indexed from zero, sized by you, with no bounds checking. Strings are char arrays terminated by '\0', and every string function follows that contract. fgets and snprintf read and build text safely; strlen, strcmp, and friends do the rest; and when an array crosses into a function it becomes a pointer to its first element. That pointer connection is where the next two posts go: structs and enums first, then pointers themselves.
/sponsor
Enjoyed this post? You can sponsor me and this site.