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:
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() {
message[] = ;
message[] = ;
(, message);
;
}
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:
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);
}
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
word[] = ;
word[] = ;
word[] = ;
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); |
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.
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.