string이 문자열의 크기 외에 필요로 하는 메타 정보가 무엇인지, 또 어떤 원리로 조작에 활용되는지 알아보기 위한 귀납적 시도입니다.
0. 0개의 인자
cast calldata "callThis()"
함수 식별자만 출력합니다. - 0x243da18c
1-1. 1개의 인자, uint256
cast calldata "callThis(uint256)" 1
1개의 인자에 대해 32바이트가 추가됩니다.
0x860fb2c1
0x0000000000000000000000000000000000000000000000000000000000000001
// uint256: 1
이후 zero-padding은 생략합니다.
1-2. 1개의 인자, string
cast calldata "callThis(string)" "z"
동일하게 1개의 인자이지만, 96바이트가 추가됩니다.
0x0a976363
// 함수 식별자0x20
// byte offset: 32 >0x01
- // string “z”
0x01
// 문자열 길이: 1 0x7a00000000000000000000000000000000000000000000000000000000000000
// 우측 정렬된 문자열 “z”
2. 2개의 인자, string, string
cast calldata "callThis(string, string)" "z" "a"
0x515e7093
0x40
// byte offset: 32 * 2 >0x01
(“a”)0x80
// byte offset: 32 * 4 >0x01
(“z”)- // string “z”
0x01
// 문자열 길이: 1 0x7a00000000000000000000000000000000000000000000000000000000000000
// 우측 정렬된 문자열 “z”- // string “a”
0x01
// 문자열 길이: 1 0x6100000000000000000000000000000000000000000000000000000000000000
// 우측 정렬된 문자열 “a”
3. 3개의 인자, string, uint256, string
cast calldata "callThis(string, uint256, string)" "zzz" 2 "123456781234567812345678123456780"
0x2ec90e97
0x60
// byte offset: 32 * 3 >0x21
(“12..80”)0x02
// uint256: 20xa0
// byte offset: 32 * 5 >0x03
(“zzz”)- // string “zzz”
0x03
// 문자열 길이: 30x7a7a7a0000000000000000000000000000000000000000000000000000000000
// string “zzz”- // string “12..80”
0x21
// 문자열 길이: 33 0x3132333435363738313233343536373831323334353637383132333435363738
// string “12345678” * 40x3000000000000000000000000000000000000000000000000000000000000000
// string “0”
-
전달된 인자의 순서는 고정이다.
-
bytes
,string
, 정적/가변 배열<T>[]
은 모두 가변 인자이다. 이들은 실제 데이터는 먼 곳에 저장해두고, offset을 제공하여 참조 순서를 보장한다. -
calldata를 적재하는 순간에는 뒤에 어떤 인자들이 올지 알 수 없다. 한 개의 가변 인자, 그 뒤로 수많은 정적 인자가 뒤따라 올 수도 있다. 즉, O(n)으로 calldata를 적재하려면, 모든 인자를 순서대로 읽는 순간 저장해야 한다는 뜻이다. 그렇다면 가장 합리적인 방법은 가변 인자는 유효한 메모리 영역 끝에서부터 저장하는 것이다.
- 이렇게 뒤에서부터 offset을 계산하면, 가장 최근에 적재된 가변 인자의 offset만 저장할 수 있다면, 새로운 가변 인자의 offset은 기존 offset + 현재 size로 쉽게 계산할 수 있다.
- 그렇지 않다면, 가변 인자 사이마다 입력되는 정적 인자의 개수를 고려하여야만 하고, calldata를 적재하는 입장이든, 적재된 data를 해석하는 입장이든 과정이 더 복잡해질 것은 자명하다.