Lesson 77 min read

C Strings & Characters

In C, a string is just an array of characters with a bouncer at the end: '\0'

Strings: The Hard Way

In most languages, strings are a built-in type with methods and safety nets. In C? A string is just an array of char with a special character β€” the null terminator '\0' β€” sitting at the end like a bouncer saying "the string ends here."

There's no .length property. There's no garbage collector managing memory for you. You're on your own, and every string function walks through memory character by character until it hits that '\0'. If the null terminator is missing? The function keeps walking β€” right off the edge of your data and into whatever garbage is next in memory.

Creating Strings

You have two main approaches:

  • char greeting[] = "Hello"; β€” the compiler creates a 6-element array (5 letters + '\0')
  • char greeting[6] = {'H','e','l','l','o','\0'}; β€” manual, character by character

Notice the size is 6, not 5. That null terminator needs its own locker.

Creating and Printing Strings

#include <stdio.h>
int main() {
// Compiler adds '\0' automatically
char greeting[] = "Hello";
printf("String: %s\n", greeting);
printf("Length in memory: %lu bytes\n", sizeof(greeting));
printf("Char at [0]: %c\n", greeting[0]);
printf("Char at [4]: %c\n", greeting[4]);
printf("Char at [5]: %d (null terminator)\n", greeting[5]);
return 0;
}
Output
String: Hello
Length in memory: 6 bytes
Char at [0]: H
Char at [4]: o
Char at [5]: 0 (null terminator)

The string.h Toolbox

Since C strings are just arrays, you can't use == to compare them or + to concatenate them. Instead, you use functions from <string.h>:

  • strlen(s) β€” returns the length (not counting '\0')
  • strcpy(dest, src) β€” copies src into dest
  • strcat(dest, src) β€” appends src onto the end of dest
  • strcmp(a, b) β€” compares two strings: returns 0 if equal, negative if a < b, positive if a > b

Each of these walks character by character until it hits '\0'. That means they're all O(n) β€” the longer the string, the longer they take.

string.h Functions in Action

#include <stdio.h>
#include <string.h>
int main() {
char name[] = "Alice";
char copy[20];
char full[40] = "Hello, ";
// strlen β€” get the length
printf("Length of name: %lu\n", strlen(name));
// strcpy β€” copy a string
strcpy(copy, name);
printf("Copied: %s\n", copy);
// strcat β€” concatenate strings
strcat(full, name);
strcat(full, "!");
printf("Full greeting: %s\n", full);
// strcmp β€” compare strings
printf("strcmp(\"Alice\", \"Bob\"): %d\n",
strcmp("Alice", "Bob"));
printf("strcmp(\"Alice\", \"Alice\"): %d\n",
strcmp("Alice", "Alice"));
return 0;
}
Output
Length of name: 5
Copied: Alice
Full greeting: Hello, Alice!
strcmp("Alice", "Bob"): -1
strcmp("Alice", "Alice"): 0
Note: Buffer overflow alert! strcpy and strcat don't check if the destination is big enough. If you copy a 100-character string into a 10-character buffer, it happily overwrites whatever memory comes after β€” corrupting data, crashing, or creating a security hole. Use strncpy and strncat with an explicit size limit, or better yet, use snprintf.

Character Functions

Individual characters have their own toolkit in <ctype.h>:

  • toupper('a') returns 'A'
  • tolower('Z') returns 'z'
  • isdigit('5') returns non-zero (true)
  • isalpha('x') returns non-zero (true)

Remember: a char in C is just a small integer. 'A' is 65, 'a' is 97, '0' is 48. You can do math on characters!

Manual Iteration and Character Functions

#include <stdio.h>
#include <ctype.h>
int main() {
char message[] = "Hello, World 123!";
int letters = 0, digits = 0;
// Walk character by character
for (int i = 0; message[i] != '\0'; i++) {
if (isalpha(message[i]))
letters++;
else if (isdigit(message[i]))
digits++;
}
printf("Letters: %d\n", letters);
printf("Digits: %d\n", digits);
// Convert to uppercase manually
for (int i = 0; message[i] != '\0'; i++) {
message[i] = toupper(message[i]);
}
printf("Uppercased: %s\n", message);
return 0;
}
Output
Letters: 10
Digits:  3
Uppercased: HELLO, WORLD 123!

String Literals vs. Char Arrays

There's a subtle but important difference:

  • char name[] = "Alice"; β€” creates a mutable array on the stack. You can change characters.
  • char *name = "Alice"; β€” points to a read-only string literal stored in a special section of memory. Modifying it is undefined behavior.

If you need to modify the string, use the array form. If you just need to read it (like passing to printf), a pointer to a literal is fine.

Mutable vs. Read-Only Strings

#include <stdio.h>
int main() {
// Mutable β€” lives on the stack
char arr[] = "Hello";
arr[0] = 'J'; // OK!
printf("Modified array: %s\n", arr);
// Read-only β€” points to string literal
const char *ptr = "Hello";
// ptr[0] = 'J'; // DANGER: undefined behavior!
printf("Literal pointer: %s\n", ptr);
return 0;
}
Output
Modified array: Jello
Literal pointer: Hello
Challenge

Quick check

How many bytes does char s[] = "Cat"; occupy in memory?
← ArraysFunctions β†’