Topic 98 of 100 Standard Library Functions Interactive Tutorial

C String Library Functions: Searching, Copying, Comparing, and Manipulating Text

14 mins read Last updated: 22 Aug 2026 29,600 views
C Compiler 0.00%

What is C String Library Functions? Core Concept & Direct Summary

Core Objective & Problem Statement

How do memory and string functions in `<string.h>` (`memcpy`, `memmove`, `memset`, `memcmp`, `strtok`) manipulate buffers?

Direct Answer Summary

In the C programming language, strings are not first-class primitive types. A C string is simply a contiguous sequence of characters stored in memory terminated by a null character ('\0', ASCII value 0).

Because C does not store string length alongside string data, all string processing relies on functions scanning through memory until they encounter this terminating null byte. The standard C library provides these operations via the <string.h> header.

However, <string.h> contains some of the most dangerous functions in systems programming. Historic routines like strcpy() and strcat() do not perform boundary checks, making them responsible for countless high-profile buffer overflow exploits. Even bounded variants like strncpy() have traps (such as failing to append a null terminator).

This comprehensive guide breaks down the essential <string.h> functions across four core disciplines: Copying & Concatenating, Searching & Scanning, Comparing, and Block Memory Manipulation.

How Does C String Library Functions Work? In-Depth Mechanics & Architecture

Quick Reference: Core <string.h> Functions

Architecture Diagram
                         ┌───────────────────────────────────────────────┐                         │         STANDARD C <string.h> TAXONOMY        │                         └───────────────────────────────────────────────┘
     Copy & Concatenate              Search & Scan                   Compare & Tokenize  ┌────────────────────────┐    ┌─────────────────────────┐    ┌───────────────────────────┐  │ strcpy() / strncpy()   │    │ strchr() / strrchr()    │    │ strcmp() / strncmp()      │  │ strcat() / strncat()   │    │ strstr() (substring)    │    │ strcasecmp() / _stricmp() │  │ snprintf() [stdio.h]   │    │ strspn() / strcspn()    │    │ strtok() / strtok_r()     │  │ strdup()   [POSIX]     │    │ strpbrk()               │    │ strlen()                  │  └────────────────────────┘    └─────────────────────────┘    └───────────────────────────┘                                             │                                             ▼                                Raw Memory Block Routines                                ┌─────────────────────────┐                                │ memcpy() / memmove()    │                                │ memset() / memcmp()     │                                │ memchr()                │                                └─────────────────────────┘

2. String Length: strlen() vs. sizeof()

A common bug in C string manipulation is confusing the length of the text with the size of the storage buffer.

  • sizeof(expr): A compile-time operator that returns the total bytes allocated for a variable or type.
  • strlen(str): A runtime function that counts characters sequentially up to (but not including) the terminating '\0'.
main.c
Run Online
char arr[20] = "Hello";
char *ptr = "Hello";
printf("sizeof(arr): %zu\n", sizeof(arr));
/* 20 bytes (total buffer capacity) */printf("strlen(arr): %zu\n", strlen(arr));
/* 5 bytes  (count before '\0')   */
printf("sizeof(ptr): %zu\n", sizeof(ptr));
/* 8 bytes  (pointer size on 64-bit OS) */printf("strlen(ptr): %zu\n", strlen(ptr));
/* 5 bytes  (length of literal)    */

Warning: Array Decay Trap
When you pass an array to a function, it decays into a pointer:

main.c
Run Online
void inspect(char buffer[100]) {
    printf("%zu\n", sizeof(buffer));
    /* Prints 8 (pointer size), NOT 100! */
}

Always pass buffer capacities explicitly as a separate size_t parameter.


3. Copying and Concatenation: Safety First

The Dangerous Trio: strcpy(), strcat(), and strncpy()

  1. strcpy(dest, src) & strcat(dest, src):
    BANNED IN PRODUCTION. Neither function takes the size of dest. If src is larger than dest, it writes past the buffer boundary, corrupting adjacent stack frames or heap metadata.

  2. strncpy(dest, src, n) — The Non-Termination Trap:
    Beginners assume strncpy() is the safe version of strcpy(). It is not. If the length of src is greater than or equal to n, strncpy() does NOT append a null terminator:

    main.c
    Run Online
    char dest[5];
    strncpy(dest, "HelloWorld", 5);
    /* dest contains: {
      'H', 'e', 'l', 'l', 'o'
    }
    with NO '\0'! */printf("%s\n", dest);
    /* BUFFER OVERRUN: scans memory until it hits a random 0x00 */
  3. strncat(dest, src, n) — The Bound Calculation Trap:
    strncat() appends at most n characters and always appends a null terminator. However, n represents the space remaining, not the total size of dest.

    main.c
    Run Online
    /* BUG: Passing total buffer size */strncat(dest, src, sizeof(dest));
    /* OVERFLOW! Appends up to sizeof(dest) MORE bytes */
    /* CORRECT: Compute remaining capacity */size_t current_len = strlen(dest);
    if (sizeof(dest) > current_len + 1) {
        strncat(dest, src, sizeof(dest) - current_len - 1);
    }

The Modern Industry Standard: snprintf()

Instead of juggling strncpy or strncat, use snprintf() (from <stdio.h>) for both copying and concatenation:

main.c
Run Online
char buffer[32];
/*
1. Safe Copy (guarantees null-termination and bounds enforcement) */snprintf(buffer, sizeof(buffer), "%s", "Source text");
/*
2. Safe Concatenation with formatting */char greeting[64];
const char *first = "Jane";
const char *last = "Doe";
snprintf(greeting, sizeof(greeting), "Welcome, %s %s!", first, last);

If the formatted string exceeds the buffer capacity, snprintf() truncates the string cleanly, writes the '\0' at buffer[sizeof(buffer) - 1], and returns the total number of characters that would have been written if enough space was available (allowing truncation detection).


4. Searching and Scanning Strings

The C standard library provides high-performance scanning routines implemented in assembly using vector instructions (AVX/SSE/NEON):

Architecture Diagram
                               SEARCHING FUNCTIONS IN C  ┌─────────────────────────────────────────────────────────────────────────────────┐  │ strchr(str, 'c')   ──► First occurrence of character 'c' (Left-to-Right)        │  │ strrchr(str, 'c')  ──► Last occurrence of character 'c'  (Right-to-Left)        │  │ strstr(hay, needle)──► First occurrence of substring "needle"                   │  │ strpbrk(str, "abc")──► First character matching ANY char in "abc"               │  │ strspn(str, "012") ──► Length of initial segment containing ONLY characters '0'-'2'  │ strcspn(str, "\r\n")──► Length of initial segment REJECTING characters '\r' & '\n'  └─────────────────────────────────────────────────────────────────────────────────┘

1. strchr() and strrchr(): Character Lookup

  • strchr(s, c) returns a pointer to the first occurrence of c in s, or NULL if not found.
  • strrchr(s, c) returns a pointer to the last (rightmost) occurrence.
main.c
Run Online
const char *path = "/var/log/nginx/access.log";
/* Extract file name (text after the last slash) */const char *filename = strrchr(path, '/');
if (filename != NULL) {
    printf("Filename: %s\n", filename + 1);
    /* "access.log" */
}

2. strstr(): Substring Search

Searches for a substring within a larger string:

main.c
Run Online
const char *haystack = "HTTP/1.1 404 Not Found";
char *match = strstr(haystack, "404");
if (match != NULL) {
    printf("Found at byte offset: %ld\n",(long)(match - haystack));
    /* Offset: 9 */
}

3. The Idiomatic 1-Line Newline Stripper: strcspn()

strcspn(str, reject) returns the index of the first character in str that matches any character in the reject string. This allows stripping newlines left by fgets() without loops or if branches:

main.c
Run Online
char line[128];
if (fgets(line, sizeof(line), stdin)) {
    /* Instantly replaces '\r' or '\n' with null-terminator */ line[strcspn(line, "\r\n")] = '\0';
}

5. String Comparison: strcmp() and strncmp()

Never compare C strings with ==. In C, s1 == s2 compares the memory addresses of the two pointers, not their textual contents:

main.c
Run Online
char a[] = "apple";
char b[] = "apple";
if (a == b) {
    /* ALWAYS FALSE: 'a' and 'b' reside at different stack addresses! */
}

Using strcmp()

main.c
Run Online
int result = strcmp(const char *s1, const char *s2);
Return ValueMeaningCondition
< 0 (negative)s1 is lexicographically less than s2First differing char has s1[i] < s2[i]
0Strings are identicalAll characters match up to '\0'
> 0 (positive)s1 is lexicographically greater than s2First differing char has s1[i] > s2[i]

The Unsigned Character Rule

The C standard specifies that character comparisons in strcmp() must treat characters as unsigned char. Even on systems where plain char is signed, a byte with value 0xFF is evaluated as 255, which is greater than 0x01 (1), guaranteeing consistent sort orders across architectures.

Comparing Prefixes with strncmp()

To match command names or prefixes without copying substrings:

main.c
Run Online
if (strncmp(user_input, "GET ", 4) == 0) {
    /* User entered a command starting with "GET " */
}

6. String Tokenization: strtok() vs. strtok_r()

Tokenizing splits a delimited string (e.g., CSV or log lines) into individual fields.

The Problem with Standard strtok()

strtok() has two major architectural limitations:

  1. Destructive Mutation: It modifies the input string in-place by overwriting delimiter characters with '\0'. You cannot call strtok() on read-only string literals (char *s = "a,b,c"; causes a Segmentation Fault).
  2. Not Thread-Safe: It stores parsing state across calls in a private static pointer. If two threads tokenize strings simultaneously, their tokens corrupt each other.

The Reentrant Standard: strtok_r() (POSIX) / strtok_s() (C11 / Windows)

The reentrant version requires passing a context pointer (saveptr), making it completely thread-safe:

main.c
Run Online
#include <stdio.h>
#include <string.h>
void parse_csv_line(char *line) {
    char *saveptr = NULL;
    const char *delimiters = ",";
    /* First call passes the buffer; subsequent calls pass NULL */ char *token = strtok_r(line, delimiters, &saveptr);
    while (token != NULL) {
        printf("Field: %s\n", token);
        token = strtok_r(NULL, delimiters, &saveptr);
    }
}

7. Raw Memory Functions: memcpy() vs. memmove()

When manipulating arbitrary binary buffers, structures, or non-null-terminated character blocks, use the mem* family:

  • void *memset(void *s, int c, size_t n); — Fills $n$ bytes with byte $c$ (e.g., zeroing structs).
  • int memcmp(const void *s1, const void *s2, size_t n); — Lexicographical comparison of $n$ bytes.
  • void *memcpy(void *dest, const void *src, size_t n); — Copies $n$ bytes from src to dest.
  • void *memmove(void *dest, const void *src, size_t n); — Safely copies $n$ bytes even if buffers overlap.

The Overlapping Memory Trap

main.c
Run Online
Buffer Layout: [ A | B | C | D | E | _ | _ ]Indices: 0 1 2 3 4 5 6
Task: Shift "ABCDE" right by 2 positions(copy from index 0 to index 2)Source Region : [0 .. 4]("ABCDE")Target Region : [2 .. 6](Overlaps source at indices 2, 3, and 4!)
main.c
Run Online
char buf[16] = "ABCDE";
/* WRONG: memcpy() assumes memory regions NEVER overlap! *//* In optimized code, memcpy() may read corrupted bytes that it just wrote */memcpy(buf + 2, buf, 5);
/* UNDEFINED BEHAVIOR! */
/* CORRECT: memmove() detects overlap and copies backwards if needed */memmove(buf + 2, buf, 5);
/* Guaranteed safe on all architectures */

Rule of Thumb: If there is any chance that dest and src share the same underlying buffer, always use memmove().


8. Complete Verified Demonstration Program

Below is a complete, self-contained C program demonstrating safe string formatting, substring searching, single-line newline stripping, comparison, and overlapping memory management:

main.c
Run Online
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
/*
1. Safe Formatting and Copying */
void demo_safe_copy(void) {
    printf("1. SAFE COPYING & BOUNDS ENFORCEMENT:\n");
    char dest[12];
    const char *source = "Engineering!";
    /* snprintf safely writes at most sizeof(dest) and always terminates */
    int needed = snprintf(dest, sizeof(dest), "%s", source);
    printf("   Destination content: \"%s\"\n", dest);
    printf("   Characters written : %zu (needed: %d)\n", strlen(dest), needed);
    if (needed >=(int)sizeof(dest)) {
        printf("   [Warning] String was truncated safely!\n\n");
    }
}
/*
2. Searching & Sanitization */
void demo_searching(void) {
    printf("2. SEARCHING & NEWLINE STRIPPING:\n");
    char filepath[] = "/var/log/system/kernel.log\r\n";
    /* Idiomatic 1-line newline strip with strcspn */
    filepath[strcspn(filepath, "\r\n")] = '\0';
    printf("   Cleaned path : \"%s\"\n", filepath);
    /* Locate final slash using strrchr */
    char *base = strrchr(filepath, '/');
    if (base) {
        printf("   Base filename: \"%s\"\n", base + 1);
    }
    /* Substring search with strstr */
    char *sub = strstr(filepath, "system");
    if (sub) {
        printf("   Found 'system' at byte index: %td\n\n", sub - filepath);
    }
}
/*
3. Comparing */
void demo_comparison(void) {
    printf("3. LEXICOGRAPHICAL COMPARISON:\n");
    const char *cmd = "CONNECT 192.168.1.1:8080";
    /* Prefix check with strncmp */
    if (strncmp(cmd, "CONNECT", 7) == 0) {
        printf("   Command starts with 'CONNECT'\n");
    }
    int cmp = strcmp("Beta", "Alpha");
    printf("   strcmp(\"Beta\", \"Alpha\") = %d (Beta > Alpha)\n\n", cmp);
}
/*
4. Overlapping Memory Shift */
void demo_memory_shift(void) {
    printf("4. OVERLAPPING BUFFER MANIPULATION (memmove):\n");
    char stream[16] = "0123456789";
    printf("   Initial buffer: \"%s\"\n", stream);
    /* Shift first 6 bytes ("012345") 3 bytes to the right */
    memmove(stream + 3, stream, 6);
    printf("   After memmove : \"%s\"\n", stream);
}
int main(void) {
    printf("============================================================\n");
    printf("        C STRING LIBRARY (<string.h>) VERIFIED DEMO         \n");
    printf("============================================================\n\n");
    demo_safe_copy();
    demo_searching();
    demo_comparison();
    demo_memory_shift();
    return 0;
}

9. Common String Pitfalls and Anti-Patterns

1. Off-by-One Buffer Allocation

Forgetting to account for the terminating null byte:

main.c
Run Online
/* BUG: Allocates space for 5 chars, but "hello" requires 6 bytes! */
char *str = malloc(strlen("hello"));
strcpy(str, "hello");
/* Heap metadata corruption! */
/* FIX: Always add + 1 for '\0' */
char *str = malloc(strlen("hello") + 1);

2. Modifying String Literals

String literals reside in read-only memory (.rodata):

main.c
Run Online
char *s = "hello";
s[0] = 'H';
/* CRASH: Segmentation fault */
char s[] = "hello";
/* Stack array: Modifiable */
s[0] = 'H';
/* OK */

3. Schlemiel the Painter's Algorithm (strcat in Loops)

When calling strcat() repeatedly in a loop to construct a large string:

main.c
Run Online
for (int i = 0; i < 10000; i++) {
    strcat(big_buffer, chunk);
}

strcat() must scan through the entire length of big_buffer on every iteration just to find where the string currently ends. This degrades performance from $O(N)$ to $O(N^2)$, causing seconds of latency on megabyte-sized buffers.
Fix: Keep track of the current write offset and use snprintf or memcpy.

Runnable C Code Example & Practical Implementation

main.c
Run Online
#include <stdio.h>
#include <string.h>
int main() {
    char buffer[32];
    memset(buffer, 0, sizeof(buffer));
    char source[] = "C-Language-Standard-Library";
    memcpy(buffer, source, strlen(source));
    printf("Copied buffer: %s\
", buffer);
    // Tokenize string
    printf("Tokens:\
");
    char *token = strtok(buffer, "-");
    while (token != NULL) {
        printf("  -> %s\
", token);
        token = strtok(NULL, "-");
    }
    return 0;
}

Key Takeaways, Best Practices & Memory Safety Rules

Best Practice & Key Takeaways
Use memmove() whenever copying memory between overlapping buffers (such as shifting array elements in-place).

Frequently Asked Questions & Technical Interview Insights

Technical Interview & Viva Questions

Frequently asked C programming interview questions
Q

Q: What is the critical difference between `memcpy()` and `memmove()`?

A: `memcpy()` assumes source and destination memory buffers do not overlap and is optimized for speed. `memmove()` detects overlapping memory ranges and guarantees safe copying without data corruption.
Q

Q: Is `strtok()` thread-safe?

A: No, `strtok()` maintains internal static state. In multi-threaded programs, use POSIX thread-safe `strtok_r()`.

Related Tools, Tutorials & Authoritative References

Recommended Interactive Tools

Authoritative Standards & Citations