Skip to content

C String Handling — String Manipulation with string.h

DodaTech Updated 2026-06-28 7 min read

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

  1. Buffer overflow with strcpy/strcat: These functions do not check destination size. Always use strncpy/strncat (and null-terminate manually) or snprintf.

  2. 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'.

  3. 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.

  4. 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.

  5. 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

  1. Why is snprintf safer than sprintf?
  2. What is the difference between strtok and strtok_r?
  3. Why must you cast char to unsigned char when using isalpha?
  4. How would you check if a string ends with ".txt"?
  5. 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 = value lines, 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

What is the difference between strncpy and memcpy?

strncpy copies up to n characters from a string, padding with null bytes if the source is shorter. memcpy copies exactly n bytes regardless of content. strncpy is for strings; memcpy is for arbitrary memory.

Is strncat safe to use?

strncat is safer than strcat but subtle: it appends at most n characters, but n is the remaining space minus 1, not the total buffer size. snprintf with %s is clearer and safer.

How do I split a string into multiple parts?

Use strtok_r (reentrant). For more complex parsing, use strstr to find delimiters and manually copy substrings with strncpy. For regular expressions, use the POSIX regex library (regex.h).

What is the difference between strcmp and strncmp?

strcmp compares entire strings. strncmp compares at most n characters. Use strncmp for prefix checks or when you know only the first n characters are safe to access.

{{< 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.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C