Skip to Content
Course content

39: String Manipulation Techniques

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, let's get our hands dirty. I've been working on a small system that takes user-submitted tags—you know, like " C Programming ", " Software Engineering ", or " Coding"—and the problem is that users are inconsistent with their spacing and capitalization. If I try to compare "C Programming" with " c programming ", the computer tells me they're different, which is technically true but practically useless for a search feature.

I want to build a "normalization" function. The goal: trim the leading and trailing whitespace and make everything lowercase. Let's see if we can figure this out together.

The "Read-Only" Trap

My first instinct is to just pass a string to a function and start chopping it up. Let's try a quick experiment.

char *tag = "  C Programming  ";
tag[0] = 'X'; // Let's see if this works

If I run that, I get a segmentation fault. Why? Because " C Programming " is a string literal stored in the read-only section of the binary. I can't modify it in place. I need a buffer. Let's move the input into a character array so we actually have permission to move bytes around.

char tag[] = "  C Programming  ";
// Now we can actually mutate this.

Hunting for the Real Start

First, I need to get rid of those leading spaces. I could try to create a new string, but that's a waste of memory. I'll just find where the actual text starts and shift everything over to the left.

I'll use a pointer to scan for the first non-space character:

char *start = tag;
while (*start == ' ') {
    start++;
}
// 'start' now points to the 'C'

Okay, that's easy. But now my string effectively starts in the middle of the array. To fix this, I'll use memmove. I prefer memmove over memcpy here because the source and destination regions overlap—we're shifting the string within its own memory block. memcpy with overlapping memory is undefined behavior, and that's a rabbit hole we don't want to go down today.

size_t len = strlen(tag);
size_t leading_spaces = start - tag;
memmove(tag, start, len - leading_spaces + 1); 
// The +1 ensures we grab that null terminator '\0'

Cutting Off the Tail

Now the front is clean, but we still have those trailing spaces at the end. This is actually easier. I don't need to move any memory; I just need to find the last non-space character and plant a null terminator right after it.

I'll start from the end of the string and walk backward:

int end = strlen(tag) - 1;
while (end >= 0 && tag[end] == ' ') {
    tag[end] = '\0';
    end--;
}

Wait, I noticed something. If I put the '\0' inside the loop, I'm overwriting every single trailing space with a null terminator. It works, but it's redundant. I only need to do it once at the very end of the search. Let's refine that.

int end = strlen(tag) - 1;
while (end >= 0 && tag[end] == ' ') {
    end--;
}
tag[end + 1] = '\0';

Normalizing the Case

Finally, we need to handle the capitalization. I could write my own logic to check if a character is between 'A' and 'Z' and add 32 to it, but ctype.h already provides tolower(). It's cleaner and handles locale edge cases better than I ever would.

I'll just loop through the now-trimmed string and convert each character:

for (int i = 0; tag[i]; i++) {
    tag[i] = tolower((unsigned char)tag[i]);
}

One small detail: I cast tag[i] to (unsigned char). This is a habit I've picked up over the years. tolower expects an int that can be represented as an unsigned char or EOF. If you pass a signed char with a negative value (which happens with some extended ASCII characters), you can trigger undefined behavior. It's a tiny detail, but it's the difference between code that works on your machine and code that crashes in production.

So, we've gone from a messy, read-only literal to a cleaned-up, lowercase, in-place modified buffer. We used pointers for scanning, memmove for shifting, and ctype.h for normalization. This is the bread and butter of string manipulation in C: managing the pointers and being obsessive about the null terminator.




📋 Practical Task

Exercise: URL Slug Generator

In web development, a "slug" is a URL-friendly version of a string. For example, "Hello World! This is C" should become "hello-world-this-is-c".

Write a function void make_slug(char *str) that transforms a string in-place according to these rules:

  • Convert all uppercase letters to lowercase.
  • Replace any sequence of one or more spaces or punctuation marks (non-alphanumeric characters) with a single hyphen '-'.
  • Remove any leading or trailing hyphens.
  • Ensure the resulting string is properly null-terminated.

Test Case:
Input: " C Programming: The Complete Guide! "
Expected Output: "c-programming-the-complete-guide"

Rating
0 0

There are no comments for now.

to be the first to leave a comment.