Educational Blog

How to Use Strings in C

Learn C string basics, safe input, and core string functions.

Here is a practical guide to strings in C, built around the most useful mental model: a C string is just a char array that ends with a null terminator \0. Once that clicks, the rest of string handling becomes much easier to reason about.

What a string really is in C

Unlike higher-level languages, C does not have a built-in string type. A string is a sequence of characters stored in memory, with a \0 byte marking the end.

That means this is a string:

char name[] = {'C', 'o', 'd', 'e', '\0'};

And this is also a string:

char name[] = "Code";

The compiler automatically adds the null terminator for you in the second example. That terminator is not optional. Without it, standard string functions keep reading past the intended end of the text.

Why the null terminator matters

The null byte is how functions like printf, strlen, strcpy, and strcmp know where the string ends. If you forget it, you do not get a clean syntax error. You get undefined behavior, which is worse because the program may appear to work until it suddenly does not.

Creating strings safely

There are several common ways to create strings in C, and each one has tradeoffs.

FormExampleNotes
String literalchar s[] = "Hello";Mutable array copy with automatic terminator
Pointer to literalchar *s = "Hello";Should not be modified
Character arraychar s[20] = "Hello";Fixed-size buffer with extra space
Manual initializationchar s[] = {'H','i','\0'};More explicit, less convenient

A string literal stored through a pointer is especially important to understand. This works for reading:

char *s = "Hello";
printf("%s\n", s);

But writing to s[0] is not safe, because the literal may live in read-only memory. If you need to modify the text, use an array instead:

char s[] = "Hello";
s[0] = 'J';

Common string operations

The standard library in <string.h> gives you the core tools you will use most often.

strlen

strlen counts the number of visible characters before the terminator.

#include <string.h>

int main(void) {
    char text[] = "banana";
    printf("%zu\n", strlen(text));
    return 0;
}

strlen("banana") returns 6, not 7, because the null terminator is excluded.

strcpy and strncpy

strcpy copies one string into another buffer.

char dest[20];
strcpy(dest, "hello");

The main rule is simple: dest must be large enough. If it is too small, the copy overruns the buffer.

strncpy is often discussed as a safer alternative, but it has its own sharp edges because it may not null-terminate the destination if the source is long. In practice, buffer-size discipline matters more than memorizing a single function as “safe.”

strcat

strcat appends one string to another.

char message[50] = "Hello";
strcat(message, " world");

Again, the destination buffer must have room for the existing text, the new text, and the terminator.

strcmp

strcmp compares two strings lexicographically.

if (strcmp(a, b) == 0) {
    printf("Match\n");
}

It does not return a boolean. It returns 0 when the strings are equal, a negative value when the first is smaller, and a positive value when the first is larger.

Reading strings from input

Input is where many beginners get tripped up. scanf("%s", ...) reads one word and stops at whitespace, so it is not suitable for full lines.

Example with fgets

fgets is usually the better default for line-based input.

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

int main(void) {
    char buffer[100];

    if (fgets(buffer, sizeof buffer, stdin) != NULL) {
        buffer[strcspn(buffer, "\n")] = '\0';
        printf("You typed: %s\n", buffer);
    }

    return 0;
}

This example reads a whole line and then removes the trailing newline if one was stored.

Why fgets is better than gets

gets is removed from modern C because it cannot check buffer length. If you see it in old code, treat it as a bug, not a convenience.

String buffers and array size

A string buffer is just storage allocated for future text. The size decision matters.

If you declare this:

char name[10] = "Alex";

You have room for 9 visible characters plus \0. If the user later types something longer than the buffer, you need a strategy to reject, truncate, or resize.

Practical rules

  • Leave room for the null terminator.
  • Never use a destination buffer larger than you can track confidently.
  • Check input lengths before copying.
  • Prefer bounded operations where possible, but still validate the result.

Useful functions to know

Here is a compact reference for functions you will use repeatedly.

FunctionPurposeTypical caution
strlenMeasure lengthExcludes \0
strcmpCompare textReturns 0 on equality
strcpyCopy textDestination must fit
strcatAppend textDestination must fit
strchrFind a characterReturns pointer or NULL
strstrFind a substringWatch for NULL result

A good habit is to treat each call as a memory question, not just a text question. Ask yourself: where is the data stored, how large is the buffer, and who owns it?

Pointers and strings

Strings in C are tightly connected to pointers. When you pass a string to a function, you usually pass a pointer to its first character.

void print_upper(const char *s) {
    while (*s != '\0') {
        putchar(*s);
        s++;
    }
}

This loop walks character by character until it reaches the terminator. That style is very common in C because strings are memory-first, not object-first.

const char * versus char *

If a function only reads a string, declare the parameter as const char *. That tells the caller and the compiler that the function will not modify the characters.

void log_message(const char *message);

This is a small change, but it improves API clarity and prevents accidental writes to read-only data.

Character arrays vs string literals

A frequent source of confusion is the difference between an array and a pointer.

char a[] = "hi";
char *b = "hi";

a is an array containing its own copy of the characters. b points to a string literal. You can think of a as storage you own and b as a reference to storage you should not alter.

That distinction explains many bugs in beginner code. If you need writable text, use an array. If you only need to read text, a const char * is usually the right shape.

Handling common mistakes

Most string bugs in C come from a short list of patterns.

1. Forgetting the terminator

A string that is not terminated with \0 is not a valid C string.

2. Writing past the buffer

If you copy or append more text than the destination can hold, you corrupt memory.

3. Assuming scanf reads a full line

It stops at whitespace unless you use a different format strategy.

4. Comparing with ==

Use strcmp, not pointer equality, when checking string content.

5. Modifying string literals

Treat literals as read-only unless you have explicitly copied them into a mutable array.

A small example program

This example reads a name, stores it safely, and prints a greeting.

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

int main(void) {
    char name[64];

    printf("Enter your name: ");
    if (fgets(name, sizeof name, stdin) == NULL) {
        return 1;
    }

    name[strcspn(name, "\n")] = '\0';

    if (strlen(name) == 0) {
        printf("No name entered.\n");
        return 0;
    }

    printf("Hello, %s!\n", name);
    return 0;
}

This version avoids the most common input mistakes. It limits the read size, strips the newline, and checks for empty input.

How to get comfortable with strings

The fastest way to improve is to write tiny programs that focus on one behavior at a time.

Try these exercises:

  1. Read a line and count its characters.
  2. Compare two strings and print which one is longer.
  3. Convert lowercase letters to uppercase in place.
  4. Split a sentence on spaces.
  5. Copy a string into a second buffer without overflowing it.

When you can explain what each character occupies in memory, string code stops feeling mysterious.

Bottom line

Strings in C are simple in concept and strict in practice. They are arrays of characters terminated by \0, and almost every mistake comes down to memory boundaries, missing terminators, or misunderstanding pointers. If you focus on buffer size, mutability, and function behavior, you can use strings confidently without relying on guesswork.

Written by

c-double.com Editorial Team

Editorial team

c-double.com publishes practical how-to guides and educational articles with clear steps and useful context.