String Functions
Master the C standard library string functions from string.h - strlen, strcpy, strcat, strcmp, and more. Learn safe alternatives, common pitfalls, and how to manipulate strings effectively.
String Library Overview
The C standard library provides string manipulation functions in string.h. These functions operate on null-terminated character arrays, performing copying, concatenation, comparison, searching, and more. Understanding these functions is essential for string manipulation in C, though many have safer modern alternatives.
#include <string.h>
#include <stdio.h>
/* Common string functions:
strlen() - Get string length
strcpy() - Copy string
strncpy() - Copy n characters
strcat() - Concatenate strings
strncat() - Concatenate n characters
strcmp() - Compare strings
strncmp() - Compare n characters
strchr() - Find character
strstr() - Find substring
strtok() - Tokenize string
*/String Length - strlen()
strlen() returns the number of characters before the null terminator, not including it. It counts until finding '\0', so the string must be properly terminated. Time complexity is O(n) - it scans the entire string.
/* strlen - get string length */
char str[] = "Hello";
size_t len = strlen(str);
printf("Length: %zu\n", len); // 5 (not 6!)
/* Length vs size */
printf("strlen: %zu\n", strlen(str)); // 5
printf("sizeof: %zu\n", sizeof(str)); // 6 (includes \0)
/* Empty string */
char empty[] = "";
printf("Length: %zu\n", strlen(empty)); // 0
/* Unterminated string (DANGEROUS) */
char bad[5] = {'H','e','l','l','o'}; // No \0!
// strlen(bad); // UNDEFINED: Reads past array
/* Performance consideration */
/* BAD: Calculates length every iteration */
for (size_t i = 0; i < strlen(str); i++) {
printf("%c", str[i]);
}
/* GOOD: Calculate once */
size_t length = strlen(str);
for (size_t i = 0; i < length; i++) {
printf("%c", str[i]);
}
/* Manual implementation */
size_t my_strlen(const char *str) {
const char *start = str;
while (*str) {
str++;
}
return str - start;
}
/* Safe version with max length */
size_t safe_strlen(const char *str, size_t maxlen) {
size_t len = 0;
while (len < maxlen && str[len] != '\0') {
len++;
}
return len;
}String Copy - strcpy() and strncpy()
strcpy() copies a string including the null terminator. It's unsafe because it doesn't check destination size. strncpy() limits the copy but doesn't guarantee null termination. Modern code should use snprintf or safer alternatives.
/* strcpy - copy string (UNSAFE) */
char dest[20];
char src[] = "Hello";
strcpy(dest, src); // Copies "Hello\0"
printf("%s\n", dest); // Hello
/* DANGER: Buffer overflow */
char small[5];
// strcpy(small, "Hello World"); // OVERFLOW!
/* strncpy - copy n characters */
char dest2[20];
strncpy(dest2, "Hello World", 5); // Copies "Hello"
dest2[5] = '\0'; // MUST manually terminate!
/* strncpy peculiarities */
char d1[10];
strncpy(d1, "Hi", sizeof(d1)); // Pads rest with \0
// d1 = {'H','i','\0','\0','\0','\0','\0','\0','\0','\0'}
char d2[10];
strncpy(d2, "HelloWorld!", 10); // No null terminator!
// d2 = {'H','e','l','l','o','W','o','r','l','d'}
d2[9] = '\0'; // Must add manually
/* Safe copy function */
void safe_strcpy(char *dest, const char *src, size_t dest_size) {
if (dest_size == 0) return;
strncpy(dest, src, dest_size - 1);
dest[dest_size - 1] = '\0';
}
/* Modern alternative: snprintf */
char buf[20];
snprintf(buf, sizeof(buf), "%s", "Hello World");
// Always null-terminates, never overflows
/* Copy with length check */
int strcpy_checked(char *dest, size_t dest_size, const char *src) {
size_t src_len = strlen(src);
if (src_len >= dest_size) {
return -1; // Not enough space
}
strcpy(dest, src);
return 0;
}
/* Manual implementation */
char* my_strcpy(char *dest, const char *src) {
char *ret = dest;
while ((*dest++ = *src++));
return ret;
}String Concatenation - strcat() and strncat()
strcat() appends one string to another. Like strcpy, it's unsafe without size checking. strncat() limits the append but has confusing behavior. Always ensure destination has enough space for the result plus null terminator.
/* strcat - concatenate strings */
char dest[20] = "Hello";
strcat(dest, " World"); // dest now "Hello World"
printf("%s\n", dest);
/* DANGER: Buffer overflow */
char small[10] = "Hello";
// strcat(small, " World!"); // OVERFLOW!
/* strncat - concatenate n characters */
char d[20] = "Hello";
strncat(d, " World", 3); // Appends " Wo"
printf("%s\n", d); // Hello Wo
/* strncat auto null-terminates (unlike strncpy) */
char d2[20] = "Hi";
strncat(d2, " there", 10); // Appends " there\0"
/* Safe concatenation */
int safe_strcat(char *dest, size_t dest_size, const char *src) {
size_t dest_len = strlen(dest);
size_t src_len = strlen(src);
if (dest_len + src_len >= dest_size) {
return -1; // Not enough space
}
strcat(dest, src);
return 0;
}
/* Building strings safely */
char buffer[100] = ""; // Initialize empty
if (safe_strcat(buffer, sizeof(buffer), "Hello") == 0 &&
safe_strcat(buffer, sizeof(buffer), " ") == 0 &&
safe_strcat(buffer, sizeof(buffer), "World") == 0) {
printf("%s\n", buffer); // Hello World
}
/* Modern alternative: snprintf for concatenation */
char buf[50] = "Hello";
size_t len = strlen(buf);
snprintf(buf + len, sizeof(buf) - len, " %s", "World");
/* Or build entire string at once */
snprintf(buf, sizeof(buf), "%s %s", "Hello", "World");
/* Manual implementation */
char* my_strcat(char *dest, const char *src) {
char *ret = dest;
while (*dest) dest++; // Find end
while ((*dest++ = *src++));
return ret;
}String Comparison - strcmp() and strncmp()
strcmp() compares strings lexicographically, returning negative if first is less, zero if equal, positive if first is greater. Never use == to compare strings - it compares addresses, not content. strncmp() compares only the first n characters.
/* strcmp - compare strings */
char s1[] = "apple";
char s2[] = "banana";
char s3[] = "apple";
int result1 = strcmp(s1, s2);
if (result1 < 0) {
printf("%s comes before %s\n", s1, s2); // apple < banana
}
int result2 = strcmp(s1, s3);
if (result2 == 0) {
printf("%s equals %s\n", s1, s3); // apple == apple
}
/* WRONG: Using == */
if (s1 == s3) { // Compares ADDRESSES, not content
// May or may not execute (undefined)
}
/* RIGHT: Using strcmp */
if (strcmp(s1, s3) == 0) { // Compares content
printf("Strings are equal\n");
}
/* Case-sensitive comparison */
printf("%d\n", strcmp("Hello", "hello")); // Non-zero (different)
/* Case-insensitive comparison (non-standard but common) */
#include <strings.h> // Or <string.h> on some systems
printf("%d\n", strcasecmp("Hello", "hello")); // 0 (equal)
/* strncmp - compare n characters */
char str1[] = "Hello World";
char str2[] = "Hello There";
if (strncmp(str1, str2, 5) == 0) {
printf("First 5 chars match\n"); // Prints this
}
if (strcmp(str1, str2) == 0) {
printf("Full strings match\n"); // Doesn't print
}
/* Sorting with strcmp */
void sort_strings(char **strings, int count) {
for (int i = 0; i < count - 1; i++) {
for (int j = i + 1; j < count; j++) {
if (strcmp(strings[i], strings[j]) > 0) {
char *temp = strings[i];
strings[i] = strings[j];
strings[j] = temp;
}
}
}
}
/* Usage */
char *words[] = {"banana", "apple", "cherry"};
sort_strings(words, 3);
// Now: {"apple", "banana", "cherry"}
/* Return value meaning */
/*
strcmp(s1, s2) < 0 → s1 comes before s2
strcmp(s1, s2) == 0 → s1 equals s2
strcmp(s1, s2) > 0 → s1 comes after s2
*/
/* Manual implementation */
int my_strcmp(const char *s1, const char *s2) {
while (*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(unsigned char*)s1 - *(unsigned char*)s2;
}Searching - strchr(), strstr(), strrchr()
String searching functions find characters or substrings. strchr() finds first occurrence of a character, strrchr() finds last, and strstr() finds a substring. They return pointers to the found location or NULL.
/* strchr - find character */
char str[] = "Hello World";
char *ptr = strchr(str, 'o');
if (ptr != NULL) {
printf("Found 'o' at position %ld\n", ptr - str); // 4
printf("Rest of string: %s\n", ptr); // o World
}
/* Not found */
ptr = strchr(str, 'z');
if (ptr == NULL) {
printf("Character not found\n");
}
/* Find all occurrences */
char text[] = "Hello World";
char *p = text;
while ((p = strchr(p, 'o')) != NULL) {
printf("Found 'o' at position %ld\n", p - text);
p++; // Move past this occurrence
}
/* strrchr - find last occurrence */
char path[] = "/usr/local/bin/program";
char *filename = strrchr(path, '/');
if (filename != NULL) {
filename++; // Skip the '/'
printf("Filename: %s\n", filename); // program
}
/* strstr - find substring */
char haystack[] = "The quick brown fox";
char *needle = strstr(haystack, "brown");
if (needle != NULL) {
printf("Found at position %ld\n", needle - haystack); // 10
printf("Substring: %s\n", needle); // brown fox
}
/* Check if string contains substring */
if (strstr(haystack, "quick") != NULL) {
printf("Contains 'quick'\n");
}
/* Find and replace (by copying) */
char source[] = "Hello World";
char target[] = "World";
char replacement[] = "Universe";
char result[100] = "";
char *pos = strstr(source, target);
if (pos != NULL) {
/* Copy part before match */
size_t prefix_len = pos - source;
strncpy(result, source, prefix_len);
result[prefix_len] = '\0';
/* Append replacement */
strcat(result, replacement);
/* Append rest after match */
strcat(result, pos + strlen(target));
printf("%s\n", result); // Hello Universe
}
/* Case-insensitive search (non-standard) */
char* stristr(const char *haystack, const char *needle) {
size_t needle_len = strlen(needle);
while (*haystack) {
if (strncasecmp(haystack, needle, needle_len) == 0) {
return (char*)haystack;
}
haystack++;
}
return NULL;
}Tokenization - strtok()
strtok() splits a string into tokens based on delimiters. It modifies the original string by replacing delimiters with null terminators. Not thread-safe - use strtok_r() for reentrant code. Useful for parsing CSV, command-line input, and configuration files.
/* strtok - tokenize string */
char str[] = "apple,banana,cherry";
char *token;
/* First call: Pass string */
token = strtok(str, ",");
while (token != NULL) {
printf("Token: %s\n", token);
/* Subsequent calls: Pass NULL */
token = strtok(NULL, ",");
}
// Prints: apple, banana, cherry
/* strtok modifies original string! */
char original[] = "a,b,c";
printf("Before: %s\n", original); // a,b,c
strtok(original, ",");
// Now original is modified internally
/* Multiple delimiters */
char text[] = "word1 word2,word3;word4";
token = strtok(text, " ,;");
while (token != NULL) {
printf("%s\n", token);
token = strtok(NULL, " ,;");
}
/* Parse CSV line */
void parse_csv(char *line) {
char *field = strtok(line, ",");
int col = 0;
while (field != NULL) {
printf("Column %d: %s\n", col++, field);
field = strtok(NULL, ",");
}
}
/* Thread-safe version: strtok_r */
char str2[] = "a:b:c";
char *saveptr;
char *token2;
token2 = strtok_r(str2, ":", &saveptr);
while (token2 != NULL) {
printf("%s\n", token2);
token2 = strtok_r(NULL, ":", &saveptr);
}
/* Safer alternative: Manual tokenization */
void split_string(const char *str, char delimiter) {
const char *start = str;
const char *end;
while ((end = strchr(start, delimiter)) != NULL) {
printf("%.*s\n", (int)(end - start), start);
start = end + 1;
}
/* Print last token */
printf("%s\n", start);
}
/* Count tokens */
int count_tokens(const char *str, const char *delimiters) {
char *copy = strdup(str); // Don't modify original
int count = 0;
char *token = strtok(copy, delimiters);
while (token != NULL) {
count++;
token = strtok(NULL, delimiters);
}
free(copy);
return count;
}Other Useful String Functions
The string library includes additional functions for specific tasks. Understanding the full toolkit helps you solve string problems efficiently.
#include <string.h>
#include <ctype.h>
/* strspn - span of characters in set */
char str[] = "12345abc";
size_t num_len = strspn(str, "0123456789");
printf("Number length: %zu\n", num_len); // 5
/* strcspn - span NOT in set */
char input[] = "Hello, World!";
size_t alpha_len = strcspn(input, " ,!");
printf("First word length: %zu\n", alpha_len); // 5
/* strpbrk - find any character from set */
char text[] = "Hello World";
char *punct = strpbrk(text, ",.!?");
if (punct == NULL) {
printf("No punctuation found\n");
}
/* memcpy - copy memory (not just strings) */
char src[] = "Hello";
char dest[10];
memcpy(dest, src, 6); // Copies including \0
/* memmove - safe for overlapping memory */
char buf[] = "Hello World";
memmove(buf + 2, buf, 5); // Safe even if overlap
printf("%s\n", buf); // HeHello
/* memcmp - compare memory */
if (memcmp("Hello", "Hello", 5) == 0) {
printf("Equal\n");
}
/* memset - set memory to value */
char buffer[100];
memset(buffer, 0, sizeof(buffer)); // Zero entire buffer
memset(buffer, 'A', 10); // Fill first 10 with 'A'
/* memchr - find byte in memory */
char data[] = "Hello\0World"; // String with embedded \0
char *pos = memchr(data, 'W', sizeof(data));
if (pos != NULL) {
printf("Found at %ld\n", pos - data); // 6
}
/* strdup - duplicate string (allocates memory) */
char *original = "Hello";
char *copy = strdup(original); // Allocates and copies
if (copy != NULL) {
printf("%s\n", copy);
free(copy); // Must free!
}
/* String transformation */
void to_uppercase(char *str) {
while (*str) {
*str = toupper(*str);
str++;
}
}
void to_lowercase(char *str) {
while (*str) {
*str = tolower(*str);
str++;
}
}
/* Trim whitespace */
char* trim(char *str) {
/* Trim leading */
while (isspace(*str)) {
str++;
}
if (*str == '\0') {
return str;
}
/* Trim trailing */
char *end = str + strlen(str) - 1;
while (end > str && isspace(*end)) {
end--;
}
end[1] = '\0';
return str;
}Summary & What's Next
Key Takeaways:
- ✅ strlen() gets length (without \\0)
- ✅ strcpy() and strcat() are unsafe - no bounds checking
- ✅ strncpy() and strncat() are safer but have quirks
- ✅ Use strcmp() to compare strings, not ==
- ✅ strchr() finds character, strstr() finds substring
- ✅ strtok() splits strings but modifies original
- ✅ Prefer snprintf() for safe string building
- ✅ Always check buffer sizes before operations