Strings

C has no string type. A string is simply an array of char that ends with a zero byte, written '\0' and called the null terminator. Every string function relies on that terminator to know where the text stops — so making sure it's there, and that arrays are big enough to hold it, is the heart of string handling in C.

Strings are char arrays

C
#include <stdio.h>

int main(void) {
    char greeting[] = "Hello";
    printf("%s has %zu bytes\n", greeting, sizeof greeting);
    for (size_t i = 0; i < sizeof greeting; i++) {
        if (greeting[i] != '\0') {
            printf("[%zu] %3d '%c'\n", i, greeting[i], greeting[i]);
        } else {
            printf("[%zu] %3d  (the terminator)\n", i, greeting[i]);
        }
    }
    return 0;
}
Output
Hello has 6 bytes
[0]  72 'H'
[1] 101 'e'
[2] 108 'l'
[3] 108 'l'
[4] 111 'o'
[5]   0  (the terminator)

"Hello" has five letters but takes six bytes: the compiler adds the '\0' automatically. %s prints characters until it reaches that zero.

Declaring strings

C
#include <stdio.h>

int main(void) {
    char name[20] = "Ada";             // 20 bytes: "Ada", '\0', then zeros
    char word[] = "kettle";            // exactly 7 bytes
    const char *motto = "Keep calm";   // pointer to a string literal
    char letters[4] = { 'a', 'b', 'c', '\0' };

    name[0] = 'E';                     // arrays can be modified
    printf("%s | %s | %s | %s\n", name, word, motto, letters);
    return 0;
}
Output
Eda | kettle | Keep calm | abc

A string literal like "Keep calm" is stored in read-only memory for the whole program. Point to it with a const char *; trying to modify it is undefined behaviour (and often a crash). If you need to change the text, copy it into a char array.

Length vs. size

strlen (from <string.h>) counts the characters before the terminator. sizeof gives the array's capacity in bytes:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    char city[32] = "Lisbon";
    printf("strlen: %zu, sizeof: %zu\n", strlen(city), sizeof city);
    return 0;
}
Output
strlen: 6, sizeof: 32

strlen has to walk the whole string to find the zero, so call it once and store the result rather than calling it in every loop condition.

Walking through a string

Loop until you reach the terminator:

C
#include <stdio.h>

int main(void) {
    const char *text = "the quick brown fox";
    int spaces = 0;
    for (int i = 0; text[i] != '\0'; i++) {
        if (text[i] == ' ') spaces++;
    }
    printf("%d words\n", spaces + 1);
    return 0;
}
Output
4 words

You can't assign or compare with = and ==

Strings are arrays, so the usual operators don't do what you'd expect. Assigning an array is an error; comparing with == compares addresses, not text:

C
#include <stdio.h>

int main(void) {
    char status[10];
    status = "ready";
    printf("%s\n", status);
    return 0;
}
Compiler error
main.c:5:12: error: array type 'char[10]' is not assignable

And == on two arrays compares where they are in memory — two different arrays are never equal, whatever they contain. The compiler can even tell you so:

C
#include <stdio.h>

int main(void) {
    char status[10] = "ready";
    char input[10] = "ready";
    printf("%s\n", status == input ? "same" : "different");
    return 0;
}
Compiler warnings
main.c:6:27: warning: array comparison always evaluates to false [-Wtautological-compare]

The right tools are strcpy to copy text into an array and strcmp to compare it:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    char status[10];
    strcpy(status, "ready");
    char input[10] = "ready";
    printf("strcmp says %s\n", strcmp(status, input) == 0 ? "same" : "different");
    return 0;
}
Output
strcmp says same

Use strcpy (or safer alternatives) to copy and strcmp to compare — the next lesson covers them.

The terminator must fit

An array for a string needs one more byte than the number of characters. Forget it, and functions run past the end looking for a zero — a buffer overflow. When you build strings yourself, add the terminator explicitly:

C
#include <stdio.h>

int main(void) {
    char initials[4];
    const char *names[] = { "Grace", "Brewster", "Hopper" };
    for (int i = 0; i < 3; i++) {
        initials[i] = names[i][0];
    }
    initials[3] = '\0';        // without this, printf would read beyond the array
    printf("%s\n", initials);
    return 0;
}
Output
GBH

Reading strings

As lesson 15 showed, read text with fgets, which respects the array size, and strip the newline:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    char colour[16];
    printf("Favourite colour? ");
    if (fgets(colour, sizeof colour, stdin)) {
        colour[strcspn(colour, "\n")] = '\0';
        printf("\n%s is a fine choice (%zu letters).\n", colour, strlen(colour));
    }
    return 0;
}
Input
teal
Output
Favourite colour?
teal is a fine choice (4 letters).

Never use gets — it can't limit the input length and was removed from C in C11 — and always give scanf("%s") a width, such as %15s.

Text encoding

A char is one byte. Plain English letters fit in one byte each, but most other characters, such as é or emoji, take several bytes in the common UTF-8 encoding. strlen counts bytes, not visible characters:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    printf("%zu %zu\n", strlen("cafe"), strlen("café"));
    return 0;
}
Output
4 5

For full Unicode text processing, C programs use libraries such as ICU; for most everyday programs, treating UTF-8 as bytes works fine as long as you don't cut a string in the middle of a character.

Try it yourself

Reverse a string in place by swapping characters from both ends towards the middle.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
desserts