C String Handling — String Manipulation with string.h
In this tutorial, you will learn about C String Handling. We cover key concepts, practical examples, and best practices to help you master this topic.
C string handling through string.h provides functions for measuring (strlen), copying (strcpy, strncpy), concatenating (strcat, strncat), comparing (strcmp, strncmp), searching (strstr, strchr, strrchr), and tokenizing (strtok) null-terminated character arrays.
What You Will Learn
- Computing string length with strlen
- Safe copying with strncpy and snprintf
- Concatenation with strncat
- Comparison with strcmp, strncmp, strcasecmp
- Searching with strstr, strchr, strrchr, strspn, strcspn
- Tokenization with strtok and strtok_r
- Character classification with ctype.h (isalpha, isdigit, etc.)
- Building strings safely with snprintf
Why It Matters
String manipulation is the most common operation in C programs after arithmetic. Configuration file Parsing, user input validation, network protocol handling, log formatting, and text processing all depend on string functions. Using the safe variants (strncpy, strncat, snprintf) prevents buffer overflows, the most common security vulnerability in C. Durga Antivirus Pro parses virus definition files (CSV), constructs file paths for quarantine, and formats scan reports using these functions.
Real-World Use
A web server parses HTTP headers: it uses strstr to find "Content-Length:", then strtol to extract the value, strchr to find ":" delimiters, and strtok_r to parse comma-separated values in the Accept header. Without these functions, parsing would require manual character-by-character scanning.
Learning Path
flowchart LR A[Standard Library] --> B[String Handling\nYou are here] B --> C[Error Handling] style B fill:#f90,color:#fff
String Length and Copying
#include <stdio.h>
#include <string.h>
int main() {
const char *msg = "Hello, World!";
size_t len = strlen(msg);
printf("Length of '%s': %zu\n", msg, len);
// Safe copy with strncpy
char dest[10];
strncpy(dest, msg, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Always null-terminate!
printf("Truncated: '%s' (len=%zu)\n", dest, strlen(dest));
// Using snprintf for safe formatted copy
char buf[32];
int written = snprintf(buf, sizeof(buf), "Value: %s (%zu)", msg, len);
printf("snprintf wrote %d bytes: '%s'\n", written, buf);
return 0;
}
Concatenation
#include <stdio.h>
#include <string.h>
int main() {
char path[128] = "/home/user/";
const char *filename = "documents/report.txt";
// Safe concatenation
size_t remaining = sizeof(path) - strlen(path) - 1;
strncat(path, filename, remaining);
printf("Path: %s\n", path);
// Building a string step by step
char message[256] = {0};
size_t pos = 0;
int n = snprintf(message + pos, sizeof(message) - pos, "Error: ");
if (n > 0) pos += n;
n = snprintf(message + pos, sizeof(message) - pos, "%s", "File not found");
if (n > 0) pos += n;
n = snprintf(message + pos, sizeof(message) - pos, " (%s, line %d)", "config.c", 42);
if (n > 0) pos += n;
printf("Built message: '%s'\n", message);
return 0;
}
Comparison
#include <stdio.h>
#include <string.h>
int main() {
const char *a = "apple";
const char *b = "banana";
const char *c = "APPLE";
printf("strcmp(\"%s\", \"%s\") = %d\n", a, b, strcmp(a, b));
printf("strcmp(\"%s\", \"%s\") = %d\n", b, a, strcmp(b, a));
printf("strcmp(\"%s\", \"%s\") = %d\n", a, a, strcmp(a, a));
// Compare first 3 characters
printf("strncmp(\"%s\", \"%s\", 3) = %d\n", a, b, strncmp(a, b, 3));
// Case-insensitive (POSIX extension)
printf("strcasecmp(\"%s\", \"%s\") = %d\n", a, c, strcasecmp(a, c));
// Check prefixes
const char *url = "https://example.com";
if (strncmp(url, "https://", 8) == 0) {
printf("URL uses HTTPS\n");
}
// Check suffixes
const char *filename = "image.jpg";
size_t flen = strlen(filename);
if (flen >= 4 && strcmp(filename + flen - 4, ".jpg") == 0) {
printf("JPEG file detected\n");
}
return 0;
}
Searching
#include <stdio.h>
#include <string.h>
int main() {
const char *text = "The quick brown fox jumps over the lazy dog";
const char *word = "fox";
// Find substring
char *found = strstr(text, word);
if (found) {
size_t pos = found - text;
printf("Found '%s' at position %zu\n", word, pos);
}
// Find character from start
char *ch = strchr(text, 'q');
if (ch) {
printf("First 'q' at position %ld\n", ch - text);
}
// Find character from end
ch = strrchr(text, 'o');
if (ch) {
printf("Last 'o' at position %ld\n", ch - text);
}
// Span: find first character NOT in set
const char *digits = "123abc456";
size_t span = strspn(digits, "0123456789");
printf("Leading digits: %zu ('%.*s')\n", span, (int)span, digits);
// Find first character IN set (complement of strspn)
const char *email = "user@example.com";
size_t cspan = strcspn(email, "@");
printf("Username: '%.*s'\n", (int)cspan, email);
return 0;
}
Tokenization
#include <stdio.h>
#include <string.h>
int main() {
char csv[] = "apple,banana,cherry,date,elderberry";
char *token;
char *rest = csv;
int count = 0;
printf("CSV tokens:\n");
while ((token = strtok_r(rest, ",", &rest))) {
printf(" %d: %s\n", ++count, token);
}
// strtok_r is reentrant (thread-safe)
char line1[] = "one|two|three";
char line2[] = "alpha;beta;gamma";
char *save1 = NULL, *save2 = NULL;
char *t1 = strtok_r(line1, "|", &save1);
char *t2 = strtok_r(line2, ";", &save2);
while (t1 || t2) {
if (t1) { printf("From line1: %s\n", t1); t1 = strtok_r(NULL, "|", &save1); }
if (t2) { printf("From line2: %s\n", t2); t2 = strtok_r(NULL, ";", &save2); }
}
return 0;
}
Character Classification
#include <stdio.h>
#include <ctype.h>
int main() {
const char *text = "Hello 123!@# World";
for (const char *p = text; *p; p++) {
unsigned char ch = (unsigned char)*p;
if (isalpha(ch)) {
printf("'%c' is a letter (alpha)\n", ch);
} else if (isdigit(ch)) {
printf("'%c' is a digit\n", ch);
} else if (isspace(ch)) {
printf("'%c' is whitespace\n", ch);
} else if (ispunct(ch)) {
printf("'%c' is punctuation\n", ch);
}
}
// Case conversion
char upper[] = "Hello World";
for (char *p = upper; *p; p++) {
*p = toupper(*p);
}
printf("Uppercase: %s\n", upper);
// Check string properties
const char *username = "alice_123";
int valid = 1;
for (const char *p = username; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '_') {
valid = 0;
break;
}
}
printf("Username '%s' %s valid\n", username, valid ? "is" : "is not");
return 0;
}
Common Mistakes
Buffer overflow with strcpy/strcat: These functions do not check destination size. Always use strncpy/strncat (and null-terminate manually) or snprintf.
Forgetting null termination: strncpy does not null-terminate if the source is longer than the buffer. Always ensure null termination:
buf[sizeof(buf) - 1] = '\0'.Using strtok in multithreaded code: strtok uses a static internal buffer, making it non-reentrant and thread-unsafe. Always use strtok_r in threaded code.
Assuming strlen result includes the null terminator: strlen returns the number of characters before the null terminator. The actual buffer needs strlen(s) + 1 bytes.
Passing char to ctype.h functions without casting: Functions like isalpha() take an int that must be representable as unsigned char or EOF. Passing a plain char (which may be signed on some platforms) causes undefined behavior for negative values. Cast to
(unsigned char).
Practice Questions
- Why is snprintf safer than sprintf?
- What is the difference between strtok and strtok_r?
- Why must you cast char to unsigned char when using isalpha?
- How would you check if a string ends with ".txt"?
- Challenge: Write a function that sanitizes user input by removing all characters that are not alphanumeric, underscore, hyphen, or period. Use isalnum and pointer arithmetic. Then write a function that validates an email address: it must have exactly one @, local part before @ is non-empty, domain has at least one dot, no special characters beyond alphanumeric, dot, hyphen, underscore.
Mini Project
Build a configuration file parser:
- Format:
key = valuelines, blank lines, comments starting with#or; - Handle: leading/trailing whitespace, quoted values ("value with spaces")
- Functions:
config_load(filename),config_get(key),config_get_int(key, default),config_get_bool(key, default),config_free() - Use: fgets for reading, strchr for finding '=', strtok for trimming, strcasecmp for key matching
- Support: variable expansion in values:
${HOME}replaced with getenv("HOME") - Error handling: duplicate key warning, malformed line error with line number
- Store as a Linked List of key-value pairs
FAQ
{{< faq "How do I format a number as a string?" "Use snprintf: snprintf(buf, sizeof(buf), \"%d\", 42). For large numbers, use %zu for size_t, %ld for long, %lld for long long, and PRIu64 from inttypes.h for uint64_t." >}}What is Next
Proceed to Error Handling to learn about errno, perror, strerror, and robust error management. Then continue with Signals for asynchronous event handling.