GOMTU Crypto
guidePart 39 of 41 in this guide

EVM Memory Explained: Layout, Expansion Gas, and Solidity Safety

Learn how EVM memory works, why expansion costs gas, how Solidity uses the free memory pointer, and which assembly mistakes can corrupt execution.

GOMTU
GOMTU
Crypto Research · September 15, 2026 · 8 min read
Share𝕏in
EVM Memory Explained: Layout, Expansion Gas, and Solidity Safety

A Solidity function can look harmless yet consume surprising gas—or behave incorrectly in hand-written assembly—because one temporary workspace is easy to overlook. EVM memory is that workspace. Understanding it connects practical blockchain basics to the bytes a contract reads, changes, hashes, returns, and then discards.

Think of memory as a fresh roll of graph paper issued to each call frame. The contract can write notes anywhere on the roll, but unrolling farther costs gas. When that call ends, the paper is shredded. This guide explains the layout Solidity expects, the current expansion formula, and the safety boundaries that matter when you inspect or write low-level code.

What EVM memory is

Advertisement

During contract execution, the EVM has several data areas with different lifetimes. The Ethereum EVM documentation describes memory as a transient, byte-addressed workspace. It is separate from the 256-bit stack and from persistent contract storage.

Each message-call frame gets its own memory. A child call does not directly share the caller's memory; call opcodes copy designated input bytes into the child context and later copy return bytes back. Memory starts zeroed for a new frame and disappears with that frame, whether execution succeeds or reverts.

Data areaTypical purposeWritable?Lifetime
StackOperands and small valuesYesCurrent call frame
MemoryMutable temporary bytesYesCurrent call frame
CalldataExternal call inputNoCurrent call frame
StorageContract stateYesPersists across transactions
Transient storageTemporary contract stateYesCurrent transaction

This distinction changes both semantics and cost. Copying a reference type from calldata or storage into memory creates an independent copy, while a memory-to-memory assignment can create another reference to the same object. Solidity documents those rules under data location and assignment behavior.

How Solidity lays out memory

The EVM itself exposes a flat byte array. Solidity adds conventions so compiled code and inline assembly agree about what each region means. Its official memory-layout documentation reserves the first 128 bytes:

Byte rangeSolidity convention
0x00–0x3f64 bytes of scratch space for short-lived operations such as hashing
0x40–0x5fThe word containing the free memory pointer
0x60–0x7fThe zero slot, used as the initial pointer for empty dynamic arrays
0x80…The normal allocation area

The value stored at 0x40 initially points to 0x80. High-level Solidity places new objects at the current free pointer and moves it forward; it does not provide a built-in way to free memory during the call.

Dynamic memory arrays begin with a 32-byte length followed by their elements. Most array elements occupy a multiple of 32 bytes even when their Solidity type is smaller, although bytes and string are packed exceptions. A fixed-size memory array has no length word. These rules differ from Ethereum contract storage slots, where small adjacent values can share a persistent 32-byte slot.

Warning

Do not assume unused space at the free memory pointer is zero. Solidity may use temporary memory beyond that pointer without advancing it, and its documentation explicitly warns that this area may not be zeroed.

MLOAD, MSTORE, and the highest byte rule

Three basic opcodes make the model concrete:

  • MLOAD(offset) reads 32 bytes beginning at offset.
  • MSTORE(offset, value) writes a full 32-byte word.
  • MSTORE8(offset, value) writes the lowest byte of value.

Memory grows when an operation accesses a non-empty range beyond the currently active size. The new size is rounded up to a 32-byte word boundary. It is the highest byte reached—not how many isolated bytes you intended to use—that determines expansion. A write at a very large offset can therefore demand all memory up to that point and run out of gas.

Memory is also consumed by operations that copy calldata or return data, hash a memory region, emit log data, call another contract, return successfully, or revert with data. Their opcode or per-word charges are separate from the incremental cost of expanding memory.

How EVM memory expansion gas is calculated

Under Ethereum's current EVM schedule, the total cost for an active memory size of a 32-byte words is:

Cmem(a) = 3 × a + floor(a² / 512)

The Ethereum Yellow Paper defines this formula. An opcode pays only the difference between the cost after and before its access:

expansion cost = Cmem(words after) - Cmem(words before)

That means expanding in several small steps does not avoid the eventual cumulative charge. The quadratic term is zero through 22 words and first becomes nonzero at 23 words. It then matters increasingly as the highest accessed offset grows.

Active memoryWordsTotal Cmem gas
128 bytes412
704 bytes2266
736 bytes2370
1,024 bytes3298
4,096 bytes128416

These figures are raw EVM memory-expansion gas, not a user's transaction fee. The actual fee also depends on every other opcode, calldata, state access, the gas used, base fee, and priority fee. A complete gas-fee calculation must include that larger context.

The official execution-spec implementation independently expresses the same word rounding, linear term, quadratic term, and incremental charge. Fork rules can evolve, so gas-sensitive tooling should follow the target network's active specification rather than treat a blog formula as an eternal constant. For example, EIP-7686 proposes linear limits but is marked Stagnant; it is not the live rule described above.

A safe allocation pattern in inline assembly

Solidity's inline-assembly documentation gives the core allocation pattern:

function allocate(uint256 length) pure returns (uint256 pos) {
    assembly ("memory-safe") {
        pos := mload(0x40)
        mstore(0x40, add(pos, length))
    }
}

Real allocation code normally rounds a byte length up when later objects require word alignment and checks that arithmetic cannot wrap. The important sequence is simple: read the pointer, reserve a non-overlapping region, then update the pointer before Solidity can allocate another object there.

The memory-safe annotation is not a runtime guard. It is a promise to the compiler that the block touches only permitted regions: Solidity-managed memory, properly allocated memory, the 0x00–0x3f scratch region, or qualifying temporary space beyond the pointer. The compiler may enable additional optimizer behavior based on that promise. A false annotation can cause incorrect, undefined behavior that tests may not expose.

Risks and common mistakes

Clobbering reserved slots

Leaving a changed value at 0x40 can make later allocations overlap. Writing to the zero slot at 0x60 can corrupt the default representation of empty dynamic arrays. Scratch space is temporary; values there must not be assumed to survive across high-level operations.

Confusing aliases with copies

Two memory variables can point to the same object. Mutating through one reference may change what the other sees. By contrast, moving data across memory, calldata, and storage boundaries can trigger a copy. Confirm the language's assignment rule instead of reasoning from variable names.

Expanding to an attacker-controlled offset

If untrusted input influences an assembly offset or length without bounds, one access can force extreme expansion and revert out of gas. Validate ranges before addition and before memory access. Test worst-case inputs, not just average ones.

Treating temporary as shared transaction state

Memory belongs to one call frame. It is not a channel for sharing a lock across nested calls or callbacks. When state must survive across call frames but only for the transaction, transient storage has different semantics and its own security considerations.

Practical review checklist

  • Identify whether each reference value is in memory, calldata, or storage
  • Keep the 0x40 free pointer valid and the 0x60 zero slot intact
  • Reserve non-overlapping regions before writing dynamic data
  • Bound user-controlled offsets and lengths before arithmetic
  • Include copy costs and expansion deltas in gas analysis
  • Treat memory-safe as a compiler promise, not a safety check
  • Test large arrays, revert data, return data, and nested calls
  • Verify gas assumptions against the target fork and compiler version

FAQ

Is EVM memory persistent?

No. It belongs to a call frame and is discarded when that frame ends. Persistent contract values belong in storage; transaction-scoped, cross-call temporary state uses transient storage where appropriate.

Is memory always cheaper than storage?

They solve different problems. Memory avoids persistent state writes, but copying and expansion still cost gas. A large or sparse access can be expensive. Compare compiled bytecode and realistic inputs instead of applying a universal slogan.

Does reading memory expand it?

Yes, if a nonzero-length read reaches beyond the active size. Expansion is based on the highest byte the operation needs, rounded to 32-byte words.

Can Solidity memory be freed during a call?

High-level Solidity currently allocates forward and does not free objects. The whole frame's memory disappears when the call returns or reverts.

Primary sources

Treat memory as a bounded execution resource

EVM memory is temporary, but it is not free or structureless. Solidity's pointer conventions keep objects from colliding, while Ethereum's expansion schedule charges for the highest region a frame reaches. Read the data location, follow the free pointer, bound offsets and lengths, and measure the compiled path against the active fork.

This article is educational and not financial advice. Smart-contract bugs and crypto assets can cause complete loss. Verify current primary documentation, test before deploying or signing, use only amounts you can afford to lose, and do your own research (DYOR).

Advertisement

Keep learning

Explore related topics

More from GOMTU