Aller au contenu principal
BlockchainMar 28, 2026

Deep EVM #6 : Gestion mémoire en Yul — mstore, mload et pointeur de mémoire libre

OS
Open Soft Team

Engineering Team

La mémoire dans l’EVM

La mémoire EVM est un tableau d’octets linéaire qui commence vide et s’étend dynamiquement. Elle est volatile — effacée à la fin de chaque contexte d’appel. L’accès se fait par mots de 32 octets avec mstore/mload, ou octet par octet avec mstore8.

mstore et mload en détail

mstore(offset, value)

Écrit un mot de 32 octets à la position offset en mémoire :

mstore(0x00, 0x42)  // Écrit 0x42 (rembourré à 32 octets) à la position 0
mstore(0x20, 0xff)  // Écrit 0xff à la position 32

Important : mstore écrit toujours 32 octets. mstore(0x00, 1) écrit 31 octets de zéros suivis de 0x01.

mload(offset)

Lit 32 octets depuis la position offset :

let value := mload(0x00)  // Lit memory[0x00..0x20]

Le pointeur de mémoire libre

Solidity stocke le pointeur de mémoire libre à 0x40. C’est un compteur qui indique où commence la mémoire inutilisée.

function allocate(size) -> ptr {
    ptr := mload(0x40)              // Position actuelle
    mstore(0x40, add(ptr, size))    // Avancer le pointeur
}

Ce pattern est l’équivalent EVM de malloc(). En Yul pur, vous pouvez ignorer ce pointeur et gérer la mémoire manuellement, mais si vous mélangez Yul et Solidity, respectez-le.

Expansion mémoire et coûts

La mémoire s’étend automatiquement quand vous écrivez au-delà de sa taille actuelle. Le coût suit une formule quadratique :

cout_gas = 3 * mots + mots^2 / 512

mots = ceil(taille_max_accédée / 32).

Pour les petites allocations (< 724 octets), c’est essentiellement 3 gas par mot. Au-delà, le coût quadratique explose rapidement. C’est pourquoi vous ne devez jamais allouer de grandes zones mémoire sans nécessité.

Patterns d’utilisation mémoire

Pattern 1 : Zone scratch (0x00-0x3f)

Les 64 premiers octets sont l’espace scratch de Solidity. En Yul pur, utilisez-les pour les calculs temporaires :

// Hacher une valeur
mstore(0x00, value)
let hash := keccak256(0x00, 0x20)

Pattern 2 : Construire des données de retour

// Retourner un uint256
mstore(0x00, result)
return(0x00, 0x20)

Pattern 3 : Préparer un appel externe

// Préparer calldata pour transfer(address,uint256)
mstore(0x00, 0xa9059cbb)                    // Sélecteur
mstore(0x04, recipient)                       // Adresse
mstore(0x24, amount)                          // Montant
let success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)

Pattern 4 : Copier calldata en mémoire

// Copier les arguments de la transaction
calldatacopy(0x00, 0x04, 0x40)  // Copier 64 octets depuis la position 4 du calldata
let arg1 := mload(0x00)
let arg2 := mload(0x20)

Pièges courants

  1. Écraser 0x40 — Si vous utilisez Yul inline dans Solidity, n’écrasez jamais le pointeur de mémoire libre.
  2. Chevauchement mémoire — mstore écrit 32 octets ; écrire à des positions non alignées peut écraser des données adjacentes.
  3. Expansion coûteuse — Accéder à une position mémoire très élevée coûte cher même si vous n’utilisez pas les octets intermédiaires.

Conclusion

La gestion mémoire en Yul est un outil puissant mais demande de la rigueur. Comprendre mstore/mload, le pointeur de mémoire libre et les coûts d’expansion vous permet d’écrire des smart contracts significativement plus efficaces en gas.

Dans le prochain article, nous couvrirons les boucles et conditionnels efficaces en gas dans Yul.