C Strings
A C string is text stored in a character array. Unlike languages with a special built-in string object, C represents a string as a sequence of char values ending with a special zero byte called the null terminator. This design is simple and fast, but it means you must understand buffer sizes and where the string ends.
Strings matter because almost every useful program handles names, commands, file paths, messages, or user input. In C, working with text means working carefully with arrays, indexes, and library functions.
Overview: How C Strings Work
A string literal such as "cat" contains four characters in memory: 'c', 'a', 't', and '\0'. The final '\0' is the null terminator. It is not printed as part of the text; it is a marker that tells string functions where the text stops.
Because a C string is stored in an array, the array must have room for both the visible characters and the terminator. The word "hello" needs at least 6 bytes: five letters plus '\0'. If you write char word[] = "hello";, the compiler counts the characters and creates an array of size 6. If you write char word[6] = "hello";, you provide the size yourself. But char word[5] = "hello"; is not large enough for a valid string.
The standard I/O functions also use this convention. printf("%s", word) begins at the address of the first character and keeps reading until it finds '\0'. Functions from <string.h>, such as strlen, work the same way. This is why missing terminators and buffers that are too small cause serious bugs: the function has no separate length value unless you provide one.
A char array can hold changing text, but a string literal should be treated as read-only. Use char title[] = "C"; when you want a modifiable array. Use const char *title = "C"; when you want to point at fixed literal text.
Syntax
char name[size]; /* character buffer */
char word[] = "text"; /* compiler includes '\0' */
char word[10] = "text"; /* extra unused space remains */
printf("%s\n", word); /* print as a string */
size_t len = strlen(word); /* count characters before '\0' */
| Part | Meaning |
|---|---|
char name[size] |
Allocates a fixed-size character array. |
"text" |
A string literal that includes a hidden null terminator. |
'\0' |
The byte value zero, used to mark the end of a C string. |
%s |
A printf conversion that prints characters until '\0'. |
strlen |
Returns the number of visible characters before the terminator, not the buffer capacity. |
Examples
Create and inspect strings
#include <stdio.h>
#include <string.h>
int main(void) {
char word[] = "C";
char name[6] = {'A', 'd', 'a', '\0'};
printf("word: %s\n", word);
printf("name: %s\n", name);
printf("strlen(name): %zu\n", strlen(name));
printf("sizeof name: %zu\n", sizeof name);
return 0;
}
Output:
word: C
name: Ada
strlen(name): 3
sizeof name: 6
The array word has two bytes: 'C' and '\0'. The array name has six bytes of capacity, but the string ends after Ada. That is why strlen(name) is 3, while sizeof name is 6.
Build text safely with snprintf
#include <stdio.h>
int main(void) {
const char first[] = "Ada";
const char last[] = "Lovelace";
char full[32];
int needed = snprintf(full, sizeof full, "%s %s", first, last);
printf("Full name: %s\n", full);
printf("Characters needed: %d\n", needed);
printf("Buffer size: %zu\n", sizeof full);
return 0;
}
Output:
Full name: Ada Lovelace
Characters needed: 12
Buffer size: 32
snprintf receives the destination buffer and its size. It writes at most enough characters to leave room for '\0'. Its return value tells how many characters would have been needed, not counting the terminator, so you can detect truncation in larger programs.
Loop through and modify characters
#include <stdio.h>
#include <string.h>
int main(void) {
char text[] = "C strings are arrays";
int vowels = 0;
for (size_t i = 0; i < strlen(text); i++) {
char ch = text[i];
if (ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u' ||
ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U') {
vowels++;
}
if (text[i] >= 'a' && text[i] <= 'z') {
text[i] = (char)(text[i] - 'a' + 'A');
}
}
printf("%s\n", text);
printf("Vowels: %d\n", vowels);
return 0;
}
Output:
C STRINGS ARE ARRAYS
Vowels: 5
This program treats the string like the array it is. Each index accesses one character. Because text is an array initialized from a literal, the program may change its elements. The loop stops before the null terminator, so the marker remains in place.
Read formatted text into a string buffer
#include <stdio.h>
int main(void) {
char line[] = "Grace 42";
char name[16];
int score = 0;
if (sscanf(line, "%15s %d", name, &score) == 2) {
printf("Name: %s\n", name);
printf("Score: %d\n", score);
}
return 0;
}
Output:
Name: Grace
Score: 42
The width 15 in %15s is important. It leaves one byte in the 16-byte buffer for '\0'. Without a width limit, a long input word could write past the end of name.
How It Works Step by Step
When the compiler sees char text[] = "cat";, it creates an array large enough for c, a, t, and '\0'. The variable text refers to the first element of that array in most expressions. When you pass it to printf with %s, the function receives the address of text[0].
printf prints text[0], then text[1], and continues until it reads a zero byte. strlen performs a similar walk but counts the characters instead of printing them. Neither function knows the declared array size, so the terminator is the contract that makes the bytes meaningful as a string.
This also explains why sizeof and strlen answer different questions. sizeof buffer is known from the array object in the current scope and reports storage capacity in bytes. strlen(buffer) runs at execution time and reports text length before the first terminator.
Common Mistakes
Forgetting space for the terminator
A five-letter word needs a six-byte string buffer. If you plan to store "apple", use at least char word[6]. A buffer that only fits the visible letters is not a valid C string because there is no place for '\0'.
Using unsafe input widths
The pattern scanf("%s", name) is dangerous when name is a fixed array, because the input length is unlimited. Prefer a bounded conversion such as scanf("%15s", name) for a char name[16], or read a whole line with fgets and then parse it.
Modifying a string literal
A pointer such as char *p = "hello" points at literal storage on many systems. Writing through it has undefined behavior. If you need to edit characters, create an array: char p[] = "hello".
Confusing assignment with copying
After declaration, arrays cannot be assigned with name = "Ada". Use initialization at declaration time, or use a bounded copy function when changing existing storage. Remember that the destination still needs enough room for the terminator.
Best Practices
- Always include space for the null terminator when choosing a buffer size.
- Use
sizeof bufferwhile the object is still an actual array in scope; after passing it to a function, also pass its capacity as a separate argument. - Prefer
snprintffor building strings because it receives the destination size. - Limit
%sinput with a field width, or usefgetsfor line-based input. - Use
const char *for string literals you do not intend to modify. - Use
strlenfor the current text length, not for buffer capacity. - Keep strings terminated after manual character operations.
Practice Exercises
- Create a
chararray containing your first name. Print the name, itsstrlen, and the array capacity fromsizeof. - Write a program that counts spaces in
"C strings need care"and prints the count. - Use
snprintfto build a product label from a name and a price, then print whether the output fit in the buffer.
Summary
- A C string is a
chararray whose text ends at'\0'. - String literals include the terminator automatically.
- The visible length and the buffer capacity are different ideas.
- String functions depend on a valid terminator and do not automatically know your array size.
- Safe C string code controls buffer sizes, input widths, and copying carefully.
