Arrays and Strings
What if you want to store five test scores? You could make five separate variables:
int score1 = 90;
int score2 = 85;
int score3 = 92;
int score4 = 88;
int score5 = 95;That’s annoying. What if you had 100 scores? 100 variables?
There’s a better way.
Arrays: Lists of Values
An array is a list of values stored together. Like a row of mailboxes, all next to each other.
int scores[5] = {90, 85, 92, 88, 95};This creates five integer storage spaces. They’re all named scores, but numbered 0 through 4.
scores[0] = 90
scores[1] = 85
scores[2] = 92
scores[3] = 88
scores[4] = 95Very important: Array positions start at 0, not 1.
In an array of 5 items, the positions are 0, 1, 2, 3, and 4. There is no position 5.
This confuses everyone at first. It becomes natural with practice.
Using Arrays
You access individual items using square brackets and the position number:
int scores[5] = {90, 85, 92, 88, 95};
printf("First score: %d\n", scores[0]); // Prints: 90
printf("Third score: %d\n", scores[2]); // Prints: 92
scores[1] = 87; // Change the second score from 85 to 87You can also make an empty array and fill it later:
int numbers[3]; // Make space for 3 integers
numbers[0] = 10; // Put values in
numbers[1] = 20;
numbers[2] = 30;Array Dangers
C does not check if you’re accessing a valid position.
If you try to access scores[10] in a 5-element array, C won’t stop you. Your program might crash. Or worse - it might keep running with garbage data.
int scores[5] = {90, 85, 92, 88, 95};
printf("%d\n", scores[10]); // BAD! Position 10 doesn't exist!This scares people who learned other languages first. Those languages have “safety guards” that stop you. C trusts you to be careful.
Is this dangerous? Yes. That’s why you double-check your array positions. C gives you power. Respect it.
Finding the Array Size
If you don’t know how big an array is, you can calculate it:
int scores[] = {90, 85, 92, 88, 95};
int length = sizeof(scores) / sizeof(scores[0]);
printf("Array has %d elements\n", length); // Prints: 5Here’s what’s happening:
sizeof(scores)tells you the total bytes the array usessizeof(scores[0])tells you the bytes one element uses- Divide them to get the number of elements
Don’t worry if this feels like magic. Just remember the pattern for now.
Strings: Text
C doesn’t have a special type for text. Instead, text is just an array of characters - a char array.
char name[10] = "Alice";This looks simple, but something critical is happening. Here’s what’s actually stored in memory:
name[0] = 'A' (65 in ASCII)
name[1] = 'l' (108)
name[2] = 'i' (105)
name[3] = 'c' (99)
name[4] = 'e' (101)
name[5] = '\0' (0) <- THE NULL TERMINATOR
name[6] = ??? <- unused/garbage
name[7] = ??? <- unused/garbage
name[8] = ??? <- unused/garbage
name[9] = ??? <- unused/garbageThe Null Terminator: Understanding \0
The \0 is the null character - ASCII value 0. It’s the most important character in C programming.
Why does C need this? Unlike some languages, C doesn’t store the length of a string anywhere. Instead, every function that works with strings reads characters one by one until it hits a \0. That’s how it knows where the string ends.
// This is what strlen() does internally:
int my_strlen(char *s) {
int count = 0;
while (s[count] != '\0') { // Keep going until we hit null
count++;
}
return count;
}What happens without the null terminator? Disaster. The function keeps reading memory forever (or until it randomly finds a zero byte somewhere):
char bad[3];
bad[0] = 'H';
bad[1] = 'i';
// FORGOT bad[2] = '\0';
printf("%s\n", bad); // Prints "Hi" plus GARBAGE until it finds a random 0
strlen(bad); // Returns wrong length
strcpy(dest, bad); // Copies garbage, possibly overflowing destThis is a buffer over-read vulnerability - reading memory you shouldn’t. It has caused real security bugs in real software.
The null terminator takes space. A string of 5 characters needs 6 bytes: 5 for the characters, 1 for \0. Always account for this:
char word[5] = "Hello"; // BUG! "Hello" is 5 chars + 1 null = 6 bytes needed!
char word[6] = "Hello"; // Correct: room for H e l l o \0Remember: '\0' is NOT the same as '0':
| Character | ASCII Value | Meaning |
|---|---|---|
'\0' | 0 | Null terminator - marks end of string |
'0' | 48 | The digit zero - a printable character |
char terminator = '\0'; // ASCII 0 - invisible, ends strings
char digit = '0'; // ASCII 48 - the character "0"
// These are completely different!Creating Strings
Several ways to make strings:
// Let C figure out the size (includes room for \0)
char greeting[] = "Hello!"; // Size is 7: H e l l o ! \0
// Specify a size (must be big enough for text + \0)
char name[20] = "Bob"; // Lots of extra room
// Empty string (just the null character)
char empty[] = ""; // Size is 1: just \0Printing Strings
Use %s to print a string:
char greeting[] = "Hello!";
printf("%s\n", greeting); // Prints: Hello!Notice: no [0] or anything. Just the array name. printf knows to print characters until it hits the \0 marker.
Reading Strings
Use scanf with %s to read a word:
char name[50];
printf("What is your name? ");
scanf("%s", name);
printf("Hello, %s!\n", name);Important: No & before name! This is the one exception. Arrays are already “addresses” (we’ll explain this later).
But there’s a problem: scanf with %s stops at the first space. If someone types “John Smith”, you only get “John”.
For whole lines (with spaces), use fgets instead:
char name[50];
printf("What is your name? ");
fgets(name, 50, stdin);
printf("Hello, %s!\n", name);Don’t worry about understanding fgets completely right now. Just know it reads a whole line.
String Dangers
Strings in C cause many bugs. Here are the common problems:
Forgetting the Null Terminator
If you make a string manually, you must add the \0:
char word[4];
word[0] = 'H';
word[1] = 'i';
word[2] = '\0'; // Don't forget this!Without the \0, printf keeps reading memory until it finds a zero somewhere. That’s bad.
Buffer Overflow
If someone types more characters than your array can hold, bad things happen:
char name[5]; // Only room for 4 letters + \0
scanf("%s", name); // User types "Alexander"...
// OVERFLOW! Extra characters overwrite other memory!This is called a buffer overflow. It’s a major source of security bugs in real software.
Safer version:
char name[50];
scanf("%49s", name); // Read at most 49 characters (leave room for \0)The %49s limits how many characters scanf reads.
String Functions
C has built-in functions for working with strings. Include <string.h> to use them:
#include <string.h>
char word[] = "Hello";
int len = strlen(word); // Length: 5 (doesn't count \0)Common string functions:
| Function | What it does |
|---|---|
strlen(s) | Returns the length of string s |
strcpy(dest, src) | Copies src into dest |
strcat(dest, src) | Adds src to the end of dest |
strcmp(a, b) | Compares two strings (0 if equal) |
Example:
#include <stdio.h>
#include <string.h>
int main(void) {
char first[20] = "Hello";
char second[20] = "World";
char result[40];
// Copy first into result
strcpy(result, first);
// Add a space
strcat(result, " ");
// Add second
strcat(result, second);
printf("%s\n", result); // Prints: Hello World
printf("Length: %d\n", strlen(result)); // 11
return 0;
}Warning: You can’t compare strings with ==. Use strcmp:
char a[] = "hello";
char b[] = "hello";
if (a == b) { // WRONG! This compares memory addresses!
// ...
}
if (strcmp(a, b) == 0) { // RIGHT! strcmp returns 0 if strings match
// ...
}Arrays of Strings
What if you want to store multiple strings? Like a list of names?
A single string is char[] - an array of characters.
An array of strings is char*[] - an array of pointers to characters.
char *names[] = {"Alice", "Bob", "Charlie"};
// ^^^^^
// Each element is a char* (pointer to the first character of a string)Here’s what’s in memory:
names[0] → points to → 'A' 'l' 'i' 'c' 'e' '\0'
names[1] → points to → 'B' 'o' 'b' '\0'
names[2] → points to → 'C' 'h' 'a' 'r' 'l' 'i' 'e' '\0'Each names[i] is a pointer (char*) that holds the address of a string’s first character.
char *names[] = {"Alice", "Bob", "Charlie"};
printf("%s\n", names[0]); // Alice (the whole string)
printf("%c\n", names[0][0]); // A (first character of first string)
printf("%c\n", names[1][2]); // b (third character of "Bob")Understanding the Types
This gets confusing, so let’s be precise:
| Declaration | Type | What it is |
|---|---|---|
char c | char | A single character |
char s[] | char[] | An array of characters (a string) |
char *p | char* | A pointer to a character (can point to a string) |
char *arr[] | char*[] | An array of pointers (an array of strings) |
char **pp | char** | A pointer to a pointer to a character |
The last two are related: when you pass char *arr[] to a function, it decays to char **. This is why main can be declared either way:
int main(int argc, char *argv[]) // Array of string pointers
int main(int argc, char **argv) // Pointer to string pointer - same thing!The Command Line: argv
When you run a program from the command line, the arguments come as an array of strings:
./myprogram hello worldint main(int argc, char *argv[]) {
// argc = 3 (program name + 2 arguments)
// argv[0] = "./myprogram"
// argv[1] = "hello"
// argv[2] = "world"
// argv[3] = NULL (marks the end)
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}Each argv[i] is a char* pointing to a null-terminated string.
Why This Matters for Pointers
We’ll cover this more in Part 10, but notice the pattern:
char*is one level of indirection (pointer to data)char**is two levels (pointer to pointer to data)
You can keep adding stars: char***, char****, etc. Each star adds another level of “pointing to something that points to something.”
In practice, you rarely need more than two levels. If you see code with char***** (five stars), that’s a sign something went wrong in the design. In security circles, people joke about “five star code” - and it’s not a compliment. It means the code is so tangled with indirection that it’s nearly impossible to reason about correctly.
Complete Example
#include <stdio.h>
#include <string.h>
int main(void) {
// Array of test scores
int scores[] = {92, 85, 90, 88};
int count = sizeof(scores) / sizeof(scores[0]);
// Calculate total
int total = 0;
for (int i = 0; i < count; i++) {
total = total + scores[i];
}
// Calculate average
double average = (double)total / count;
// Store name
char name[50];
printf("Enter student name: ");
scanf("%49s", name);
// Print report
printf("\nStudent: %s\n", name);
printf("Scores: ");
for (int i = 0; i < count; i++) {
printf("%d ", scores[i]);
}
printf("\nAverage: %.1f\n", average);
return 0;
}(Don’t worry about the for loop - we’ll learn that in Part 7!)
Try It Yourself
- Create an array of your 5 favorite numbers and print them all
- Write a program that asks for your name and prints it backwards
- What happens if you try to access
scores[100]in a 5-element array? Try it. - Create two strings and compare them with
strcmp. What does it return when they’re different?
Common Mistakes
- Forgetting arrays start at 0:
scores[5]doesn’t exist in a 5-element array - Forgetting the null terminator: Strings need
\0at the end - Buffer overflow: Typing more characters than the array can hold
- Using
==to compare strings: Usestrcmpinstead - No
&with scanf for strings:scanf("%s", name)- no&needed
Next Up
In Part 6, we’ll learn about making decisions - how to make your program do different things based on conditions (if this, then that).
Enjoyed This?
If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.