C Library Masterclass: strlen, strcpy, strcat, strcmp & Security
Welcome to Phase 8 (Chapter 19): C <string.h> Standard Library & Buffer Overflow Security Masterclass! Because C does not treat strings as high-level objects with built-in methods, all string operationsβfrom finding lengths to copying, concatenating, comparing, and searching substringsβare executed via the standard <string.h> library header. In this exhaustive guide, you will master the internal algorithms, time complexities, and hardware memory movements of core string functions, analyze why unbounded functions like strcpy() caused historical cyber exploits, learn how to use bounded safe variants (strncpy, strncat, strncmp), and construct clean manual pointer-based reimplementations from scratch.
The C standard library categorizes string functions into 5 core families: Measurement, Copying, Concatenation, Comparison, and Searching:
| Function Signature | Role & Mechanism | Time Complexity | Security Status |
|---|---|---|---|
size_t strlen(const char* s) |
Scans memory sequentially counting characters until '\0'. |
$O(N)$ (Linear Scan) | β Safe (Read-Only) |
char* strcpy(char* dest, const char* src) |
Copies all characters from src to dest including '\0'. |
$O(N)$ | β οΈ UNSAFE! No buffer limit. |
char* strncpy(char* dest, const char* src, size_t n) |
Copies at most $n$ characters. β οΈ Does not null-terminate if $n \le \text{len}$! | $O(n)$ | β
Bounded (Needs manual '\0') |
char* strcat(char* dest, const char* src) |
Finds end of dest and appends src. |
$O(\text{len}_1 + \text{len}_2)$ | β οΈ UNSAFE! Potential overflow. |
char* strncat(char* dest, const char* src, size_t n) |
Appends at most $n$ chars and always appends '\0'. |
$O(\text{len}_1 + n)$ | β Bounded & Safe |
int strcmp(const char* s1, const char* s2) |
Lexicographical ASCII subtraction ($s_1[i] - s_2[i]$). Returns 0 if equal. |
$O(N)$ | β Safe (Read-Only) |
char* strchr(const char* s, int c) |
Returns pointer to first occurrence of character $c$, or NULL. |
$O(N)$ | β Safe |
char* strstr(const char* haystack, const char* needle) |
Returns pointer to first occurrence of substring needle in haystack. |
$O(N \times M)$ | β Safe |
β‘ strlen() vs sizeof() in Depth:
β’ sizeof(str): Evaluates the Total Physical RAM Buffer Size in Bytes allocated at compile time (an $O(1)$ constant value).
β’ strlen(str): Traverses RAM at runtime counting characters until it hits '\0' (an $O(N)$ dynamic operation that excludes the null terminator!).
Example: For char name[50] = "Dennis";:
- sizeof(name) = 50 Bytes (Total Stack buffer).
- strlen(name) = 6 Characters (Actual payload text length).
To prevent buffer overflows in production code, always use bounded variants and guarantee null termination:
#include <stdio.h>
#include <string.h>
int main(void) {
char source[] = "Operating Systems";
char destination[30];
// 1. Safe Bounded Copy with strncpy
strncpy(destination, source, sizeof(destination) - 1);
destination[sizeof(destination) - 1] = '\0'; // Explicit Null-Terminator Safety Guarantee!
// 2. Safe Bounded Concatenation with strncat
strncat(destination, " in C", sizeof(destination) - strlen(destination) - 1);
printf("Result String: %s\n", destination);
printf("Total Length: %zu chars | Buffer Capacity: %zu bytes\n", strlen(destination), sizeof(destination));
// 3. Substring Search with strstr
char* found = strstr(destination, "Systems");
if (found != NULL) {
printf("Substring 'Systems' found starting at index: %ld\n", found - destination);
}
return 0;
}
Understanding how standard library functions operate under the hood using raw pointer arithmetic is an essential skill for system software engineers:
π οΈ Recreating Core C String Functions From Scratch:
1. Custom my_strlen:
size_t my_strlen(const char* s) { const char* p = s; while (*p) p++; return p - s; }
2. Custom my_strcpy (The Classic Dennis Ritchie 1-Liner):
char* my_strcpy(char* dest, const char* src) { char* d = dest; while ((*d++ = *src++)); return dest; }
3. Custom my_strcmp:
int my_strcmp(const char* s1, const char* s2) { while (*s1 && (*s1 == *s2)) { s1++; s2++; } return *(const unsigned char*)s1 - *(const unsigned char*)s2; }
Q1: Why does strcmp("apple", "banana") return a negative number?
strcmp subtracts ASCII values at the first differing index. For index 0: 'a' (97) - 'b' (98) = -1. Because $-1 < 0$, it indicates that "apple" precedes "banana" in lexicographical order.
Q2: What is the critical pitfall of strncpy()?
If the source string length is greater than or equal to $n$, strncpy fills all $n$ characters WITHOUT appending a null terminator ('\0'). The destination buffer is left unterminated, causing memory leaks if printed.
Q3: Why must strcmp cast pointers to unsigned char before subtraction?
The C standard dictates that character comparisons must behave as if characters are unsigned. If plain char is signed by default on the target architecture, non-ASCII characters (values $ge 128$) could yield negative values incorrectly.
Run this string comparison and search demo in our live GCC compiler:
#include <stdio.h>
#include <string.h>
int main(void) {
char s1[] = "Linux";
char s2[] = "Linux";
printf("strcmp result: %d (0 means EXACT MATCH)\n", strcmp(s1, s2));
return 0;
}