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.
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:
-
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' -
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' -
printf("%s\n", str);starts atstr[0]and prints characters until it reaches\0. -
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
strtokmodify 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.
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
| Declaration | What it creates | Can its characters be modified? | Typical use |
|---|---|---|---|
const char *s = "text"; | Pointer to a string literal | No | Fixed messages, read-only parameters |
char *s = "text"; | Pointer to a string literal | No; writing is undefined behavior | Avoid this form for literals |
char s[] = "text"; | Writable array containing a copy | Yes, within its bounds | Small local editable strings |
char s[20] = "text"; | Larger writable array with initial text | Yes, within its bounds | Buffers that may grow |
char *s = malloc(20); | Pointer to dynamically allocated writable memory | Yes, after checking allocation | Runtime-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.
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 throughp.- 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
\0when 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.
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".
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.