Question

C String Literals vs Character Arrays: Why char * Can Segfault

csegmentation-faultc-strings

Question

Why does this C code sometimes cause a segmentation fault when it attempts to modify a string?

char *str = "string";
str[0] = 'z';
printf("%s\n", str);

The assignment can also be written as *str = 'z';.

However, this version works:

char str[] = "string";
str[0] = 'z';
printf("%s\n", str);

Why is the first declaration different from the second, and why do GCC and MSVC behave differently or produce a segmentation fault?

Short Answer

You will learn the difference between a pointer to a string literal and a writable character array initialized from that literal. In C, attempting to modify a string literal has undefined behavior; creating an array from the same text creates a separate, modifiable copy.

Concept

A string literal such as "string" is text supplied by the program itself. It includes an invisible terminating null character ('\0') and has static storage duration: it exists for the entire program run.

"string"

This declaration makes str a pointer that points at that literal:

char *str = "string";

Although the pointer type is char * for historical compatibility in C, the literal must not be changed. Writing through str attempts to modify the literal:

str[0] = 'z';  // undefined behavior

That is undefined behavior. The C language no longer specifies what must happen. A program may crash with a segmentation fault, appear to work, corrupt data, or behave differently after a compiler update or optimization change.

By contrast, this declaration creates an actual array object and initializes it with a copy of the literal's characters:

char str[] = "string";

It is equivalent in effect to creating this writable array:

char str[] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };

Because the array belongs to your program as a writable object, str[0] = 'z'; is valid. The resulting string is "ztring".

This distinction matters whenever code stores, edits, passes, or returns text. You need writable storage before modifying characters.

Mental Model

Think of a string literal as a sentence printed in a reference book at a library. You may point to the page and read it, but you are not allowed to write on it.

char *str = "string";

The pointer is like an address telling you where the reference-book page is.

A character array is your own photocopy of that page:

char str[] = "string";

You can cross out and replace letters on your own copy, because it is your writable storage. Both forms can be read as text, but only the array has editable character slots.

Take Quiz

Syntax and Examples

A C string is an array of characters ending with \0.

Pointer to a read-only string literal

Use const char * when text comes from a literal and your code only needs to read it:

#include <stdio.h>

int main(void) {
    const char *message = "Hello";

    printf("%s\n", message);
    return 0;
}

const char * prevents accidental writes through message:

message[0] = 'Y';  // compilation error: message points to const characters

Writable array initialized with text

Use an array when you intend to edit the characters:

#include <stdio.h>

int main(void) {
    char message[] = "Hello";

    message[0] = 'Y';
    printf("%s\n", message);  // Yello
    return 0;
}

The array has enough space for six characters: five visible characters plus \0.

Writable array with extra capacity

An array initialized from a literal has space only for its initial contents and the terminator unless you specify a larger size.

#include <stdio.h>

int main(void) {
    char name[20] = "Ada";

    name[3] = '!';
    name[4] = '\0';
    printf("%s\n", name);  // Ada!
    return 0;
}

Use const char * for read-only literal text and char[] or another writable buffer for text you need to change.

Step by Step Execution

Consider this valid program:

#include <stdio.h>

int main(void) {
    char str[] = "string";
    str[0] = 'z';
    printf("%s\n", str);
    return 0;
}

Step by step:

  1. char str[] = "string"; creates a local character array with these elements:

    index:  0    1    2    3    4    5    6
    value: 's'  't'  'r'  'i'  'n'  'g' '\0'
    
  2. str[0] = 'z'; replaces the first element of that array.

    index:  0    1    2    3    4    5    6
    value: 'z'  't'  'r'  'i'  'n'  'g' '\0'
    
  3. printf("%s\n", str); starts at str[0] and prints characters until it reaches \0.

  4. The output is:

    ztring
    

Now compare the pointer form:

char *str = "string";

Here, str itself is a variable that stores an address. The characters are in the string literal, not in an array created by this declaration. Reading str[0] is fine; assigning to str[0] is undefined behavior.

Real World Use Cases

  • Status and error messages: Keep fixed text as const char *.

    const char *error_message = "File not found";
    
  • Parsing input: Copy input into a writable buffer before splitting or changing it. Functions such as strtok modify their input.

    char command[] = "open report.txt";
    char *word = strtok(command, " ");
    
  • Building filenames or messages: Use an adequately sized writable array when content will be assembled or edited.

    char path[256] = "/tmp/";
    
  • Configuration labels and lookup tables: Store fixed labels as pointers to read-only literals.

    const char *modes[] = { "debug", "release", "test" };
    
  • Embedded and systems code: Literals may be placed in protected read-only memory, so modifying one can fault immediately.

Take Quiz

Real Codebase Usage

In production C code, declarations communicate whether text is intended to change.

Prefer const char * for literal text

void log_message(const char *message) {
    printf("LOG: %s\n", message);
}

log_message("Server started");

The parameter says log_message only reads the characters. It can accept literals safely and helps the compiler catch accidental writes.

Use writable buffers for transforming text

#include <ctype.h>

void uppercase_first(char *text) {
    if (text == NULL || text[0] == '\0') {
        return;
    }

    text[0] = (char)toupper((unsigned char)text[0]);
}

int main(void) {
    char name[] = "ada";
    uppercase_first(name);
}

The guard clause handles a null pointer and an empty string before accessing text[0]. The function must be called with writable memory; passing a literal would be a bug.

Make copies before calling modifying APIs

char input[] = "red,green,blue";
char *first = strtok(input, ",");

Do not write this:

char *input = "red,green,blue";
char *first = strtok(input, ",");  // undefined behavior: strtok modifies input

Teams often use const in function signatures and code reviews to make ownership and mutability clear.

Common Mistakes

Declaring a literal pointer as char *

char *name = "Ada";
name[0] = 'E';  // undefined behavior

Even though some C compilers allow the declaration, it suggests that the pointed-to characters are writable. Avoid this by writing:

const char *name = "Ada";

Believing a successful run proves the code is valid

A program may appear to modify a literal on one machine. That does not make it legal C. Undefined behavior has no reliable result. Another build mode, compiler, OS, or run may crash.

Forgetting space for the null terminator

char word[3] = "cat";  // no room for '\0'; not a usable C string

Use either inferred size or enough explicit space:

char word[] = "cat";
char other_word[4] = "cat";

Writing past the end of an array

char word[] = "cat";
word[3] = '!';  // replaces '\0', so printf("%s", word) is unsafe
word[4] = '\0'; // out of bounds: undefined behavior

For later growth, allocate a larger buffer:

char word[10] = "cat";
word[3] = '!';
word[4] = '\0';

Confusing a pointer variable with character storage

char *p;

This creates only a pointer variable. It does not create a character buffer to write into. Before writing through p, it must point to valid writable storage, such as an array or dynamically allocated memory.

Using sizeof after an array has decayed to a pointer

Inside the same scope as an array declaration, sizeof str gives the array's total byte count. When passed to a function, an array parameter is treated as a pointer, so sizeof no longer gives the original array size. Pass the buffer capacity separately when a function needs it.

Comparisons

DeclarationWhat it createsCan its characters be modified?Typical use
const char *s = "text";Pointer to a string literalNoFixed messages, read-only parameters
char *s = "text";Pointer to a string literalNo; writing is undefined behaviorAvoid this form for literals
char s[] = "text";Writable array containing a copyYes, within its boundsSmall local editable strings
char s[20] = "text";Larger writable array with initial textYes, within its boundsBuffers that may grow
char *s = malloc(20);Pointer to dynamically allocated writable memoryYes, after checking allocationRuntime-sized buffers; must be freed

char * versus const char *

Both are pointers. The difference is what code is allowed to do through them:

char *writable;
const char *read_only;

const char * means “do not modify the characters through this pointer.” The pointer itself can still be changed to point somewhere else:

const char *message = "one";
message = "two";  // valid

C versus C++

In C++, string literals have type const char[], so this is normally rejected:

char *str = "string";

C permits the assignment for historical reasons, but modification is still undefined behavior. In both languages, use a writable array when modifying the text.

Take Quiz

Cheat Sheet

/* Read-only literal text */
const char *message = "Hello";

/* Writable copy with exact required size */
char message[] = "Hello";
message[0] = 'Y';

/* Writable buffer with spare capacity */
char message[32] = "Hello";

/* Read a C string */
printf("%s\n", message);
  • A C string ends with \0.
  • A string literal must not be modified.
  • char *p = "text"; points at a literal; do not write through p.
  • Prefer const char *p = "text"; for a literal.
  • char a[] = "text"; creates a writable array copy.
  • Array writes must stay within bounds and preserve a terminating \0 when the data is used as a string.
  • str[i] and *(str + i) refer to the same character.
  • Use char * in a function parameter only when the function may validly modify caller-provided writable storage.

FAQ

Why does char *str = "string"; compile in C?

C allows the assignment for historical compatibility, even though modifying the literal is undefined behavior. Prefer const char *str = "string"; so the compiler can reject accidental writes.

Is a segmentation fault guaranteed when I modify a string literal?

No. The behavior is undefined. A segmentation fault is common when literals are stored in read-only memory, but any result is possible.

Does char str[] = "string" allocate memory dynamically?

No. In a function, it usually creates an automatic local array. The exact storage duration depends on where it is declared, but it is a real array object initialized with copied characters.

Why does the array need \0?

C library functions such as printf with %s find the end of a string by searching for \0. Without it, they read beyond the intended array, causing undefined behavior.

Can I assign a new literal to a character array later?

No. Arrays are not assignable in C.

char str[] = "one";
/* str = "two"; */  // invalid

Copy into an adequately sized array instead, for example with snprintf.

Can I change where a char * points?

Yes. A pointer variable can be reassigned, even when it is a const char *.

const char *str = "one";
str = "two";

This changes the pointer's address; it does not modify either literal.

Should I use strcpy to make a writable copy?

Only if the destination is known to be large enough. For fixed local text, char buffer[] = "text"; is simpler. For formatted copies, snprintf is often safer because it receives the destination capacity.

Take Quiz

Mini Project

Description

Build a small command normalizer that changes a command name to uppercase and separates its first word from the rest of the input. This demonstrates why text that will be modified must be stored in a writable character array rather than a string literal.

Goal

Create a program that safely normalizes "status verbose" to "STATUS" and prints the remaining argument.

Requirements

Use a writable character array initialized with "status verbose".

Take Quiz

Keep learning

Related questions

View more