Strings
Names, addresses, messages and file names are all text. Many languages have a built-in string type that manages its own memory, but C does not. In C, a string is simply an array of char values that ends with a special character, the null character '\0'.
This simple design makes strings fast and flexible, but it also puts you in charge of the memory. In this lesson you will learn how strings are stored, use the standard string functions, compare and search text, and copy strings safely without overflowing a buffer.
How C stores a string
When you write char city[] = "Jaipur";, the compiler creates an array of 7 characters: the 6 letters plus a '\0' at the end that marks where the string stops.
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| Value | 'J' | 'a' | 'i' | 'p' | 'u' | 'r' | '\0' |
#include <stdio.h>
#include <string.h>
int main(void)
{
char city[] = "Jaipur";
printf("City: %s\n", city);
printf("strlen: %zu characters\n", strlen(city));
printf("sizeof: %zu bytes\n", sizeof city);
for (size_t i = 0; city[i] != '\0'; i++) {
printf("[%c]", city[i]);
}
printf("\n");
city[0] = 'j';
printf("Changed: %s\n", city);
return 0;
}City: Jaipur strlen: 6 characters sizeof: 7 bytes [J][a][i][p][u][r] Changed: jaipur
strlen counts the characters before the '\0'; sizeof gives the whole array, including the terminator. Every string function, and printf with %s, keeps reading until it finds '\0'. If the terminator is missing, they run off the end of the array into whatever memory follows. So when you size an array for text, always add one for the '\0': a 10-digit mobile number needs char mobile[11];.
Arrays versus string literals
There are two common ways to name a string:
char name[] = "Priya";creates your own array, copied from the literal. You can change its characters.const char *name = "Priya";makesnamepoint to the literal itself, which lives in read-only memory. You can read it but not change it.
Changing a string literal, for example through char *p = "Priya"; p[0] = 'p';, is undefined behaviour and usually crashes the program. Always write const char * for pointers to literals so the compiler stops such mistakes. You will learn exactly what * means in Pointers, NULL & nullptr; for now, read const char * as "a string I will not change".
Comparing and searching
The header string.h declares the standard string functions. These are the ones you will use most:
| Function | What it does |
|---|---|
strlen(s) | Number of characters before the '\0' |
strcmp(a, b) | 0 if equal, negative if a sorts first, positive if b sorts first |
strncmp(a, b, n) | Like strcmp, but compares at most n characters |
strchr(s, c) | Finds the first character c in s |
strstr(s, t) | Finds the first place where text t appears in s |
memcpy(d, s, n) | Copies n bytes |
strdup(s) | Makes a new copy in allocated memory (standard since C23) |
#include <stdio.h>
#include <string.h>
int main(void)
{
char entered[] = "pune";
const char *saved = "Pune";
if (strcmp(entered, saved) == 0) {
printf("Same city\n");
} else {
printf("Different: strcmp is case-sensitive\n");
}
printf("Patna before Pune? %s\n", strcmp("Patna", "Pune") < 0 ? "yes" : "no");
const char *address = "14 MG Road, Pune 411001";
const char *comma = strchr(address, ',');
const char *city = strstr(address, "Pune");
if (comma && city) {
printf("After the comma: [%s]\n", comma + 1);
printf("From the city on: %s\n", city);
}
return 0;
}Different: strcmp is case-sensitive Patna before Pune? yes After the comma: [ Pune 411001] From the city on: Pune 411001
strcmp compares character codes one by one. Lowercase 'p' has a larger code than 'P', so the strings differ. Only test the sign of the result (== 0, < 0, > 0); the exact number varies between systems. strchr and strstr return the position where the match starts, so printing from there shows the rest of the string; they return a null pointer when nothing is found, which is why the if checks them first. comma + 1 starts one character after the comma.
Never compare strings with ==. For two arrays, == compares their memory addresses, which are always different, so entered == saved is false even when the text matches.
Building strings safely with snprintf
The classic functions strcpy (copy) and strcat (append) write as many characters as the source has, with no idea how big the destination is. If the source is too long, they overflow the buffer. The safest everyday tool is snprintf, which works like printf but writes into an array and never writes more than the size you give it, including the '\0'.
#include <stdio.h>
#include <string.h>
int main(void)
{
char first[] = "Isha";
char last[] = "Verma";
char full[20];
snprintf(full, sizeof full, "%s %s", first, last);
printf("Full name: %s (%zu chars)\n", full, strlen(full));
char code[8];
int needed = snprintf(code, sizeof code, "%s-%d", "ROHTAK", 2026);
printf("Stored: %s\n", code);
if (needed >= (int) sizeof code) {
printf("Truncated: needed %d characters, room for %zu\n", needed, sizeof code - 1);
}
return 0;
}Full name: Isha Verma (10 chars) Stored: ROHTAK- Truncated: needed 11 characters, room for 7
snprintf returns the length the full text would have had. If that is greater than or equal to the buffer size, the text was cut short. The result is still a valid, terminated string, and your program can decide what to do, instead of silently corrupting memory.
Buffer overflows
Here is the bug that snprintf prevents:
char name[6];
strcpy(name, "Ananya Krishnan"); // 16 bytes into a 6-byte array!strcpy writes all 15 letters and the '\0', overwriting 10 bytes of memory that belong to something else. GCC sometimes notices fixed cases like this and warns, but when the text comes from user input, nothing warns you. Overflows like this have caused real security breaches, because attackers can craft input that overwrites important data. The fixes are to size buffers generously, check lengths with strlen before copying, and use size-limited functions such as snprintf and fgets.
strncpy looks like a safe version, but it has a trap: if the source is too long, it copies exactly n characters and does not add a '\0'. Prefer snprintf(dest, sizeof dest, "%s", src).
Working with characters: ctype.h
The header ctype.h classifies and converts single characters: isalpha, isdigit, isspace, isupper, islower, toupper and tolower. Pass them an unsigned char value, because a negative char value is not allowed.
#include <ctype.h>
#include <stdio.h>
int main(void)
{
char text[] = "Train 10042 leaves Pune at 06:15";
int letters = 0, vowels = 0, digits = 0;
for (size_t i = 0; text[i] != '\0'; i++) {
unsigned char ch = (unsigned char) text[i];
if (isalpha(ch)) {
letters++;
int lower = tolower(ch);
if (lower == 'a' || lower == 'e' || lower == 'i' || lower == 'o' || lower == 'u') {
vowels++;
}
} else if (isdigit(ch)) {
digits++;
}
text[i] = (char) toupper(ch);
}
printf("%s\n", text);
printf("Letters: %d, vowels: %d, digits: %d\n", letters, vowels, digits);
return 0;
}TRAIN 10042 LEAVES PUNE AT 06:15 Letters: 17, vowels: 8, digits: 9
toupper leaves non-letters unchanged, so the digits and the colon stay as they were. This loop changes text in place, which works because text is our own array, not a string literal.
Common mistakes
- No room for
'\0'.char code[5] = "Rohan";is legal C, but the array has no terminator, soprintf("%s", code)reads past its end. Usechar code[] = "Rohan";or a size of at least 6. - Comparing with
==. Usestrcmp(a, b) == 0. - Changing a string literal. Copy it into a
chararray first. scanf("%s", ...)andstrcpywithout limits. Usefgetsandsnprintfwithsizeof.- Confusing
sizeofandstrlen.sizeofis the array's capacity;strlenis the text's current length.
Key points
- A C string is a
chararray ending with'\0'; always leave room for the terminator. strlencounts characters before'\0';sizeofgives the whole array size.- String literals are read-only; use
const char *to point at them andchar[]for text you change. - Compare with
strcmpand test only the sign of the result; search withstrchrandstrstr. - Build and copy strings with
snprintf, which never overflows and reports truncation. - Pass
unsigned charvalues to thectype.hfunctions.
Exercise
Write a function bool is_palindrome(const char *s) that returns true when a word reads the same forwards and backwards. Use two indexes, one from the start and one from the end, moving towards the middle. Test it with "malayalam", "kochi" and "level".
Show solution
#include <stdio.h>
#include <string.h>
bool is_palindrome(const char *s)
{
size_t len = strlen(s);
if (len == 0) {
return true;
}
size_t left = 0;
size_t right = len - 1;
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
}
int main(void)
{
const char *words[] = {"malayalam", "kochi", "level"};
for (int i = 0; i < 3; i++) {
printf("%s: %s\n", words[i], is_palindrome(words[i]) ? "palindrome" : "not a palindrome");
}
return 0;
}malayalam: palindrome kochi: not a palindrome level: palindrome
The empty-string check matters because len - 1 on a size_t of 0 would wrap around to a huge number. words is an array of read-only strings, which you will study further in Pointer Arithmetic & Arrays.
Quick Quiz
1. How many bytes does char word[] = "Kochi"; occupy?
Five letters plus the terminating '\0' make 6 bytes.
2. How should you check whether two strings a and b contain the same text?
strcmp compares the characters; == compares addresses.
3. What does snprintf(buf, sizeof buf, "%s", text) do if text is too long?
snprintf never writes beyond the given size and always terminates the string.
4. What is wrong with char *s = "Pune"; s[0] = 'p';?
String literals are read-only; copy the text into a char array before changing it.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
