char greeting[] = "Hello";Strings in C: Character Arrays, Declaration, Initialization, and Usage
What is Strings in C? Core Concept & Direct Summary
What is a string in C, how does null-termination (`\0`) define string boundaries, and what is the difference between char arrays and string literals?
Here's a fact that surprises almost everyone learning C: C doesn't have a string type. Not a keyword, not a built-in type, not a class. Nothing like Python's str, Java's String, or C++'s std::string.
What C has instead is a convention. A string is a char array that ends with a special character — the null terminator, '\0'. That's it. That's the whole definition. And once you internalize it, every C string behavior that seems strange suddenly makes sense: why strlen exists, why buffers need one extra slot, why forgetting the terminator crashes things, why strings can be modified in some places and not others.
This is the foundation page for our Strings cluster. Let's build the model properly from the ground up.
(Suggested image: the hero graphic — a char array box labeled "Hello" showing five characters plus a distinctly-colored '\0' box at the end, with the caption "a string IS a char array + '\0'")
How Does Strings in C Work? In-Depth Mechanics & Architecture
The Definition: A Char Array Ending in '\0'
Memory:
Six slots for five visible characters. The '\0' (null character — value 0, not the same as the character '0' which is value 48) is what marks the end.
Without it, it's not a string. It's just a character array:
These two look similar, but only the second is a proper string. Pass the first to strlen, printf("%s"), or any string.h function, and it will keep reading past the end of the array into whatever memory follows — the same runaway-buffer problem we met with gets().
That's why the double-quoted form is preferred."Hello" automatically gets its terminator; manual char-by-char initialization doesn't.
(Suggested image: side-by-side comparison of {'H','e','l','l','o'} (no terminator, marked dangerous) vs "Hello" (with terminator, marked safe) — this is the single most important visual on the page)
Every Way to Declare and Initialize a String
Here's the complete set, with what each produces.
Form 1: Array with explicit size, initialized from a literal
20 bytes total, contents 'R','a','v','i','\0', then 15 unused bytes. sizeof(name) = 20, strlen(name) = 4. The buffer has capacity for a 19-character string — the remaining bytes let you grow the text later (up to 19 characters).
Form 2: Size inferred from the literal
Exactly 5 bytes: the 4 characters plus the terminator. sizeof = 5, strlen = 4. This is fixed — you cannot append anything, because there's no spare room. Attempting strcat here overflows.
The design question between forms 1 and 2: do you need room to grow? Form 1 reserves it; Form 2 doesn't. For input, always use a sized buffer.
Form 3: Character-by-character with an explicit terminator
Equivalent to Form 2, but you must remember '\0' yourself. Valid but unnecessary in modern code.
Form 4: Zero-fill then fill later
Extremely useful for input buffers — you start with a guaranteed-valid empty string plus a generous margin.
Form 5: Empty string (valid, zero length)
One '\0' and nine unused bytes. strlen(empty) = 0. Legal and frequently used as a starting point for building strings.
Form 6: A pointer to a string literal
⚠️ This is a different beast entirely — read on.
String Literals vs Char Arrays: The Critical Difference
What's actually happening:
char arr[] = "Hello";copies the characters into a fresh array you own. You can modify it freely.char *ptr = "Hello";stores the address of a string literal that lives in read-only memory.ptris just a pointer variable.
On most modern systems, that second line causes a segmentation fault, because string literals are placed in a read-only segment. It compiles without complaint — it's a runtime surprise.
This also explains a famous crash with strtok: remember from where we split strings that strtokmodifies its input? Passing a literal crashes:
Rule worth framing on your wall:char arr[] = "text" for strings you'll modify; char *ptr = "text" only for strings you'll read. When in doubt, use the array form.
(Suggested image: two memory regions diagram — read-only literal pool (with a padlock icon) vs stack array (with a pencil icon), showing where the pointer and the array each live)
Accessing and Modifying Characters
Strings are arrays, so indexing works exactly as it did in the arrays cluster:
Traversing a string — two equally good patterns:
Both print Code. Pattern A uses the array indexing you already know; Pattern B is the pointer view (its own cluster, but you'll see it everywhere in C code).
Notice that you can't get the length with sizeof reliably once the string is passed to a function (the decay problem from the arrays cluster), but text[i] != '\0' as a loop condition always works — the terminator is in the data, not just in the declaration. That's the entire reason the '\0' convention exists: it makes strings self-terminating at runtime.
(Suggested image: both loop patterns shown side by side with the memory strip beneath, arrows showing each step)
Reading Strings From the User
This is where beginner pain concentrates, so let's be thorough.
scanf("%s", ...) — words only
Two problems:
- It stops at the first whitespace. Type "Priya Sharma" and you get "Priya" — the rest stays in the input buffer, potentially breaking your next read.
- No bounds checking by default. Type 200 characters into a 30-byte buffer and you overflow it. Always add a width limit one less than the buffer size:
When it's fine: reading single words, IDs, or short tokens where spaces genuinely aren't expected.
fgets() — full lines, safely
fgets reads until a newline (or until the buffer is full, whichever comes first), and it never overflows — the size you pass is the guarantee. This is why it's the recommended way to read text in C.
Its one quirk: it keeps the trailing newline. Strip it with the idiom you've seen throughout this series:
(Fix that a nifty strcspn trick does in one line — and why we don't just write line[strlen(line)-1] = '\0'; is worth noting: that crashes on an empty string.)
getchar() in a loop — maximum control
From the character I/O tutorials — reading characters one at a time is the fully manual option, useful when you need custom stopping rules.
And the one to NEVER use
gets has no bounds checking whatsoever and was removed from the language. fgets is its replacement. If you see gets in old code, convert it.
Quick decision guide:
Printing Strings
Three notes: printf("%s") needs no & (the array name is already an address); puts adds a newline automatically; and the %.2s precision form (from the format specifiers tutorial) is a handy way to truncate output to a fixed width.
And the reverse — printing a string character by character:
The loop-based version is what you'd use when you need to inspect or transform each character as you print.
sizeof vs strlen for Strings (One More Time)
This distinction deserves repeating because it causes so much confusion:
Practical uses: sizeof when reserving buffer space (for fgets), strlen when you want to know how much text is actually there. And notice strlen is the only one that keeps working after the array decays into a pointer — because it relies on data (the terminator), not on declaration metadata.
A Complete Example: A Simple Login System
Sample Run (success):
Sample Run (wrong password):
Everything this page taught, working together: sized char buffers (not literals, since we write into them), fgets for safe line input with sizeof giving the capacity, strcspn to strip the newline, and strcmp(...) == 0 for comparison — never ==.
The two-branch error message is a small touch worth noticing: telling the user which field was wrong requires comparing both again, which is a realistic tradeoff (a real system would deliberately not reveal which was wrong, for security — worth mentioning if you teach this).
Arrays of Strings (A Preview)
One string is a char array. Multiple strings are an array of char arrays:
That's a 2D array of characters — 7 rows, 10 columns each. It works, and it gets its own tutorial next in this cluster along with the char *names[] alternative, which has different trade-offs.
Common Mistakes
- Forgetting the
'\0'when initializing character by character — everything downstream reads past the end. - Assuming
"text"andchar arr[] = "text"are the same — one's read-only, the other's yours to modify. - Using
==to compare strings — compares addresses. Usestrcmp(...) == 0. - Size mistakes — a buffer of 5 can hold a 4-character string, not 5. The terminator needs a slot.
- Using
char name[] = "Ravi";for input — no spare capacity, so any longer input overflows. Use a sized buffer. scanf("%s")without a width limit — overflow. And it stops at spaces.- Using
gets— removed from the language for good reason. Usefgets. - Forgetting
fgetskeeps the newline — strip it withstrcspn, or your comparisons and output both get surprised. - Using
sizeofafter the string is passed to a function — you'll get the pointer size, not the buffer size. - Modifying a string returned by a function or pointed to by a literal — undefined behavior and crashes.
(Suggested image: "10 String Mistakes" checklist graphic — consistent with the series)
Final Thoughts
Strings in C reduce to one sentence: a char array whose end is marked with '\0'. Every quirk follows from that — why the terminator exists, why buffers need the extra slot, why strlen has to walk the data, why literal modifications crash, why fgets is the safe reader.
Carry these four habits with you: size your buffers with room for '\0', prefer char arr[..] over char * for anything you'll modify, read text with fgets and strip the newline, and always compare with strcmp. That's the working core of C string handling, and it covers nearly everything you'll write.
Next in this cluster: strings as arrays in practice — arrays of strings, both the char days[7][10] form and the char *days[7] form, and when each is right.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
int main() {
// Mutable stack character array
char mutableStr[] = "Code";
mutableStr[0] = 'M';
// Valid: becomes "Mode"
printf("Modified string: %s\
", mutableStr);
// Pointer to read-only string literal
const char *readOnlyStr = "Immutable";
printf("Read-only string: %s\
", readOnlyStr);
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
const char *str = "..."; so the compiler prevents accidental write attempts.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: What is the size in bytes of the string literal `"C"`?
Q: What happens if you modify a string literal via pointer: `char *s = "hello"; s[0] = 'H';`?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.