본문으로 건너뛰기
0%

Yul에서 메모리는 어떻게 작동할까?

Mark Jonathas headshot

작성자 Mark Jonathas

2023년 7월 28일에 게시됨4분 읽기

Yul스마트 컨트랙트 내부에서 일종의 어셈블리 언어를 작성할 수 있는 중급 프로그래밍 언어입니다. Yul storage와 packed storage 변수를 읽고 쓰는 방법을 배웠다면, 이제 Yul 스마트 컨트랙트에서 memory가 어떻게 동작하는지 배워야 합니다.

Yul에서 memory는 어떻게 동작하는가?

Memory는 storage와는 다르게 동작합니다. Memory는 영속적이지 않으며, 함수 실행이 끝나면 모든 변수가 지워진다는 의미입니다.

Memory는 다른 언어의 heap과 비슷하지만, garbage collector가 없습니다.

Memory는 storage보다 훨씬 저렴합니다. 처음 22 word의 memory 비용은 선형으로 계산되지만, 그 이후로는 비용이 제곱으로 증가하므로 주의해야 합니다.

Memory는 32바이트 단위로 배치됩니다. 이 부분은 나중에 더 자세히 다루겠지만, 우선 0x00 - 0x20이 하나의 단위라는 점만 이해하면 됩니다 (slot처럼 생각해도 괜찮지만 실제로는 다른 개념입니다).

Solidity는 0x00 - 0x40을 scratch space로 할당합니다. 이 영역이 비어있음이 보장되지는 않으며, 특정 연산에 사용됩니다.

0x40 - 0x60은 free memory pointer의 위치를 저장하며, 이는 memory에 새로운 값을 쓸 때 사용됩니다.

0x60 - 0x80은 gap으로 비워둡니다.

0x80부터가 실제 연산이 시작되는 지점입니다.

Memory는 값을 packing하지 않습니다. Storage에서 가져온 값은 각각 별도의 32바이트 단위에 저장됩니다 (예: 0x80-0xa0).

어떤 연산이 Yul에서 memory를 사용하는가?

Memory는 다음과 같은 연산에 사용됩니다:

  • 외부 호출의 반환 값
  • 외부 호출을 위한 함수 값 설정
  • 외부 호출에서 값 가져오기
  • 에러 문자열과 함께 revert
  • 로그 메시지
  • keccak256()으로 해시하기
  • 다른 스마트 컨트랙트 생성

memory와 관련된 유용한 Yul 명령어들입니다:

Instruction
Explanation

mload(p)

sload()와 비슷하지만, p 이후의 다음 32바이트를 불러온다는 뜻입니다

mstore(p, v)

sstore()와 비슷하지만, 값 v를 p부터 32바이트에 저장한다는 뜻입니다

mstore8(p, v)

mstore()와 비슷하지만, 단일 바이트에만 적용됩니다

msize()

접근한 memory 인덱스 중 가장 큰 값을 반환합니다

pop(x)

값 x를 버립니다

return(p, s)

실행을 종료하고, memory 위치 p - v의 데이터를 반환합니다

revert(p, s)

상태 변경을 저장하지 않고 실행을 종료하며, memory 위치 p - v의 데이터를 반환합니다

이제 몇 가지 데이터 구조를 더 살펴보겠습니다!

Yul에서 struct와 memory를 사용하는 방법

Struct와 고정 배열은 사실 동일하게 동작하지만, 고정 배열은 이미 storage 섹션에서 다뤘으므로 여기서는 struct를 살펴보겠습니다. 다음 struct를 보겠습니다.

특별할 것 없는 단순한 struct입니다.

이제 코드를 살펴보겠습니다!

여기서는 s.subVar1을 memory 위치 0x80 - 0xa0에, s.subVar2를 memory 위치 0xa0 - 0xc0에 설정하고 있습니다. 그래서 0x80 - 0xc0을 반환하는 것입니다. 트랜잭션이 끝나기 직전의 memory 레이아웃을 표로 정리하면 다음과 같습니다.

Memory Location
Value Stored

0x00

Scratch Space (Empty)

0x20

Scratch Space (Empty)

0x40

0xc0 (Free Memory Pointer)

0x60

Empty

0x80

s.subVar1: 0x20 (32 in decimal)

0xa0

s.subVar1: 0x40 (64 in decimal)

0xc0

New Free Memory Pointer. This is what msize() would return. (Empty)

여기서 기억해야 할 점:

  • 0x00 - 0x40은 scratch space로 비어 있습니다
  • 0x40은 free memory pointer를 알려줍니다
  • Solidity는 0x60을 gap으로 남겨둡니다
  • 0x80과 0xa0은 struct의 값을 저장하는 데 사용됩니다
  • 0xc0은 새로운 free memory pointer입니다

memory 섹션의 마지막 부분에서는 dynamic array가 memory에서 어떻게 동작하는지 보여드리겠습니다. 이 예제에서는 파라미터 arr로 [0, 1, 2, 3]을 전달할 것입니다. 추가로, 이 예제에서는 배열에 요소를 하나 더 추가해보겠습니다. 이렇게 하면 다른 memory 변수를 덮어쓸 수도 있으니 실제 프로덕션에서는 주의해야 합니다.

코드는 다음과 같습니다:

여기서 일어나는 일은 다음과 같습니다:

  • 배열이 memory의 어디에 저장되어 있는지 가져옵니다
  • 배열의 첫 memory 위치에 저장된 배열의 길이를 가져옵니다
  • 위치에 32바이트를 더해 (배열의 길이 부분을 건너뛰어) 다음으로 사용 가능한 위치를 확인합니다
  • 배열의 길이에 32바이트를 곱해 다음 memory 위치로 이동합니다
  • 새로운 값 (4)를 저장합니다
  • 배열의 길이를 1 증가시킵니다
  • free memory pointer를 업데이트합니다
  • 배열을 반환합니다

memory 레이아웃을 다시 한번 살펴보겠습니다.

Memory Location
Value Stored

0x00

Scratch Space (Empty)

0x20

Scratch Space (Empty)

0x40

0x140 (Free Memory Pointer)

0x60

Empty

0x80

New length of the array (6)

0xa0

arr[0] (0)

0xc0

arr[1] (1)

0xe0

arr[2] (2)

0x100

arr[3] (3)

0x120

arr[4] (4)

0x140

Free Memory Pointer (Empty)

이것으로 memory 섹션을 마칩니다!

다음으로는 Yul을 사용해 스마트 컨트랙트를 호출하는 방법을 배우거나, 다시 돌아가서 packed storage 변수를 읽고 쓰는 방법을 배워보세요!

Background gradient

블록체인 매직을 만드세요

Alchemy는 가장 강력한 Web3 개발자 제품 및 도구를 리소스, 커뮤니티, 그리고 전설적인 지원과 결합합니다.