Why does storing through a pointer to a literal string constant fault, while storing into a heap block succeeds?
answer
- permissions belong to the mapping
- literals are not scratch space
- same instruction, different region
- read-only range refuses the store
- copy into writable memory first
basics
~20 sA literal lives in the read-only constants region, mapped without write permission, so the hardware refuses the store. A heap block sits in a writable mapping, so the identical instruction succeeds. The region's permissions decide, not the pointer.
solid answer
~40 sPermissions belong to the **mapping**, not to the pointer. A literal is a build-time value that ships inside the program image and is mapped into a region with read and execute rights but no write right; a heap block sits in a writable mapping. The same store instruction through two pointers of identical type therefore succeeds in one case and traps in the other, before any byte changes. Declaring a pointer as referring to constant data is a compile-time promise and a separate mechanism - it produces a build error at best, not the run-time refusal. To modify text that began as a literal, copy the bytes into a writable block and change the copy. The read-only mapping is also what lets a toolchain merge identical literals into a single shared copy safely.
code
pseudocode · 7 linesp = address_of_literal("ready") // read-only constants region
q = allocate(5) // heap region, writable
copy_bytes(from: p, to: q, count: 5)
store_byte(q, index: 0, value: 'R') // allowed: q's range grants write
store_byte(p, index: 0, value: 'R') // refused: p's range does not
// -> protection fault, no byte changedgo deeper
Remember that a literal is not scratch space. If you need to change text, copy it into memory you asked for and change the copy.
Explain that access rights are a property of the mapped range, so the identical store succeeds or traps purely according to where the address points.
Read the fault correctly under pressure: a protection fault on a mapped, readable address points at a write into constants or code, not at a null pointer.
Treat the writable surface of a process as something to keep small on purpose; regions that are never written can be shared, merged and trusted.
## One instruction, two outcomes Take two pointers of identical type. The first was set from a literal constant written in the source. The second points at a block the program requested at run time, into which that same text was copied. Store a byte through the second and it works. Store a byte through the first and the process takes a fault. The instruction is the same, the pointer type is the same, the byte being written is the same. What differs is the **region the address falls in**, and the access rights attached to that region. ## Permissions belong to the mapping A process's address space is a list of mapped ranges, and every range carries access rights - readable, writable, executable - which the memory-protection hardware checks on every access. - The **read-only constants region** holds values fixed at build time that ship inside the program image: literal text, constant tables, jump tables. Mapped readable, not writable. - The **heap** holds blocks the program asked for at run time. Writable, because the whole point of asking for it is to put changing data in it. - A **thread's stack** holds the values of active calls. Writable, for the same reason. So a pointer is not really the subject of the question. The pointer is just an address, and the address tells you which range it lands in. The declaration that a pointer refers to constant data is a *compile-time* promise: it lets the toolchain reject an obvious mutation before the program is ever built, and it can be cast away. The run-time refusal is an entirely separate mechanism, enforced by hardware, that no cast can talk out of. ## What the fault actually is 1. The instruction issues a store to an address. 2. The protection hardware checks the rights of the range that contains that address. 3. The range grants read and execute but not write, so the access is refused **before any byte changes**, and control transfers to the operating system, which reports a protection fault. That third step is the one worth remembering during diagnosis: there is no partial write, no torn value, no half-modified literal. And it is a fault on a perfectly valid, mapped address - which is a different thing from touching an address that is not mapped at all. | Symptom | Region involved | What the pointer was | |---|---|---| | Fault on store, address readable | read-only constants, or code | valid pointer into a non-writable range | | Fault on any access, address not mapped | none | a null or wild pointer | | Silent corruption, no fault at all | heap or static data | valid pointer, wrong target | ## Why constants get their own read-only region - **De-duplication.** If nobody may write to a value, two identical literals in unrelated parts of the program can be collapsed into one copy at one address. That optimisation is only sound because the mapping forbids writes. - **Sharing between processes.** A range that is never written can be backed by one copy shared across every process running the same program. - **Catching bugs where they happen.** A buffer overrun that reaches into a writable region corrupts data and is discovered much later, somewhere else. One that reaches a read-only region stops immediately, at the instruction responsible. - **Placement beside code.** Constants and instructions have the same properties - fixed at build time, never written - so grouping them keeps the writable part of the map small and the protection boundaries simple. ## Working with it To modify text that started life as a literal, do it explicitly: reserve a writable block long enough for the text, copy the bytes into it, and modify the copy. The copy is writable purely because of where it lives. This is also why passing a literal into a routine that modifies its argument in place is a bug that no amount of argument-type adjustment fixes - the routine is fine, the storage is not. ## Where ecosystems differ Ecosystems disagree about *when* you are told, not about the region. Some hand the program a raw pointer into the constants region, so the mistake surfaces as a run-time protection fault. Others present literals as immutable values whose mutation the compiler simply rejects, so the same mistake is a build error and the fault never happens. Others again copy the value when it is modified, so the write lands on a fresh writable copy and the original constant is untouched. In all three the constants region is read-only; what changes is whether you meet that fact at build time, at run time, or never.
- How do you obtain a modifiable version of a literal?Reserve a writable block sized to the literal's length, copy the bytes into it, and work on the copy. The copy accepts stores because its range grants write permission, not because of anything about its type. The literal itself stays as it was, and may still be shared with other parts of the program.
- Why do two identical literals in different parts of a program often share one address?Because nothing may write to that region, a toolchain can merge identical constants into a single copy with no observable consequence. If the region were writable, merging would be unsound - a change through one name would alter the other. The read-only mapping is what licenses the optimisation.
saying these in an interview costs you the question
- Says the pointer's declared type decides whether a store is allowed
- Thinks the fault means the pointer was null or dangling
- Believes literals are copied into writable memory automatically
- Claims the allocator performs the write check
- Assumes a fault always means the address was unmapped