Most languages hand you a string type that knows its own length. C does not. In C a string is just an array of char with one rule attached: the text is followed by a byte with the value zero, written '\0' and called the null terminator. Everything else about C strings follows from that single convention.
A String Is a Char Array Plus '\0'
Declare a string by initializing a char array from a literal:
The array looks like four bytes, not three:
name: +-----+-----+-----+------+
| 'A' | 'd' | 'a' | '\0' |
+-----+-----+-----+------+
index: 0 1 2 3
sizeof(name) is 4. The compiler counted the characters, added one for the terminator, and sized the array for you. That extra byte is why char name[3] = "Ada"; is a bug waiting to happen - the text fits but the terminator does not, so nothing downstream can tell where the string ends.
When you want room to change the contents later, give the size yourself and leave slack:
char name[32] = "Ada"; /* 3 characters, a terminator, 28 bytes spare */
The unused bytes are zero-filled by the initializer, which is harmless.
Why the Terminator Matters
No C function receives a length. printf("%s", s) is handed one address and walks forward printing bytes until it meets '\0'. So a char array without a terminator is not a string, and treating it as one reads whatever memory happens to follow.
The rule to memorize: sizeof gives you the storage, strlen gives you the text. For char buf[32] = "Ada", sizeof buf is 32 and strlen(buf) is 3. They answer different questions and mixing them up is one of the most common C bugs.
String Literals vs Char Arrays
These two lines look similar and behave very differently:
char a[] = "hello"; /* an array you own, initialized from the literal */
char *p = "hello"; /* a pointer aimed at the literal itself */
a is a 6-byte array holding a private copy of the text. You can modify it. p points into the program's read-only string data; the literal is shared, and writing through p is undefined behavior that typically crashes at runtime rather than at compile time.
Write const char * whenever a pointer aims at a literal. The compiler then rejects the write at compile time instead of letting it become a crash. Picking between the two is really a question about pointers and arrays, covered in pointers and arrays.
One more difference worth knowing: sizeof a is 6, but sizeof p is the size of a pointer (8 on most systems) regardless of how long the text is.
Printing and Reading Strings
%s prints a string; %c prints a single character.
For input, avoid gets entirely - it was removed from the language because it cannot be used safely. Use fgets, which takes the buffer size and will not overflow:
fgets keeps the newline you pressed, which is almost never what you want in the string. The strcspn line finds the first '\n' and overwrites it with a terminator - a compact, standard way to trim it.
Walking a String Character by Character
Because the end is marked rather than counted, the idiomatic loop tests the character itself:
The condition text[i] != '\0' is often shortened to just text[i], since '\0' is zero and therefore false. Both are correct; the explicit form is easier to read while you are learning.
You can walk with a pointer instead of an index, which is equally idiomatic C:
The cast to unsigned char before calling toupper is not decoration: the <ctype.h> functions are undefined for negative values, and a plain char can be negative for bytes above 127.
You Cannot Copy a String With =
This is the wall every newcomer hits:
char a[10];
a = "hello"; /* error: assignment to expression with array type */
char b[10] = "hi";
char c[10];
c = b; /* error - same reason */
An array name is not a modifiable value, so = has nothing to do. The only place the shorthand works is initialization at declaration, which is a different operation performed by the compiler.
To copy at runtime, copy the bytes with strcpy from <string.h>:
The same reasoning explains why == does not compare strings. if (a == b) compares two addresses, so it is false for two different arrays holding identical text. Use strcmp(a, b) == 0 instead. Both functions and their safer relatives are covered in string functions.
Arrays of Strings
A list of strings is either an array of pointers (for fixed text you only read) or a two-dimensional char array (when each entry must be modifiable):
The pointer array stores three addresses into read-only literals - compact, but not modifiable. The char[3][16] form reserves 48 bytes of your own memory, so each row can be rewritten.
Common Mistakes
- Forgetting the terminator's byte.
char s[5] = "hello"has no room for'\0'. Size forstrlen + 1. - Using
sizeofwhere you meantstrlen.sizeofon a pointer gives the pointer's size, not the text's length. - Writing through a
char *to a literal. Declare such pointersconst char *. - Comparing with
==. That compares addresses. Usestrcmp. - Reading with
scanf("%s", buf). It has no size limit and overflows happily; preferfgets.
Frequently Asked Questions
How do you declare a string in C?
As a char array: char name[] = "Ada";. C has no built-in string type - that array holds 'A', 'd', 'a' and a fourth hidden byte, '\0', which marks the end. If you want room to change the text later, give the array an explicit size: char name[32] = "Ada";.
What is the null terminator in C?
The byte '\0' (value zero) that marks where a string ends. Every standard function - printf("%s"), strlen, strcpy - reads forward until it finds that byte. A char array without one is not a string, and passing it to those functions reads past the end of your memory.
Why can't I assign a string with = in C?
Because an array is not a value you can copy with =. char a[10]; a = "hi"; will not compile. Copy the characters instead with strcpy(a, "hi") from <string.h>, or initialize at declaration - char a[10] = "hi"; - which is the one place the shorthand works.
What is the difference between char * and char [] in C?
char s[] = "hi" makes a modifiable array holding a private copy of the text. char *p = "hi" makes a pointer to a string literal, which lives in read-only memory - reading it is fine, writing through it crashes. Use the array when you intend to modify, the pointer when you only read.