본문으로 건너뛰기
0%

Solidity 배열이란?

Alchemy team headshot

작성자 Alchemy

2023년 9월 22일에 게시됨4분 읽기

배열은 동일한 데이터 타입의 고정 크기 요소들을 인접하고 연속적인 메모리 위치에 저장하는 선형 데이터 구조입니다. 배열의 모든 요소는 인덱스라고 알려진 위치로 지정됩니다.

배열은 0부터 n-1까지의 인덱스 기반 시스템으로 동작하며, 여기서 n은 배열의 전체 크기를 나타냅니다. 동일한 타입의 여러 개별 변수를 생성하는 대신, 개발자는 원하는 크기의 배열 하나만 선언하면 인덱스를 통해 요소에 접근할 수 있습니다.

Ethereum 및 EVM 호환 블록체인에서 사용되는 주요 web3 프로그래밍 언어인 Solidity에서 배열은 고정 크기 또는 동적 크기가 될 수 있습니다. 객체 지향 프로그래밍 언어로서 Solidity는 스마트 컨트랙트 간 상속을 지원하며, 여러 컨트랙트를 하나의 컨트랙트로 상속받을 수 있습니다.

Solidity 배열에 대해 더 깊이 배우고 싶다면 Alchemy University의 무료 Solidity 개발자 과정에 등록하세요.

동적 배열과 고정 배열이란?

동적 배열의 크기는 선언 시 미리 정의되지 않는 반면, 고정 배열은 미리 정의된 크기를 가집니다. 요소가 순차적으로 추가됨에 따라 동적 배열의 크기가 변화하며, 런타임 중에 배열의 실제 크기가 결정됩니다.

반면, 고정 배열은 미리 정의된 크기를 가지며, 배열에 존재하는 요소의 수는 배열의 크기를 초과해서는 안 됩니다. 배열의 크기가 지정되지 않는 드문 경우에는 초기화를 담기에 '충분한 크기'의 배열이 생성됩니다.

저장소 배열 vs. 메모리 배열

Solidity에서 개발자가 고려해야 할 배열 유형에는 저장소(storage) 배열과 메모리(memory) 배열, 두 가지가 있습니다.

저장소 배열

저장소 배열은 일반적으로 상태 변수로 선언되며 고정 크기 또는 동적 크기가 될 수 있습니다. 동적 길이를 가진 저장소 배열은 크기 조정이 가능하며, 이는 push()와 pop() 함수를 사용할 수 있다는 것을 의미합니다. 이 함수들은 각각 배열에 요소를 추가하거나 제거하는 기능을 제공합니다.

메모리 배열

메모리 배열은 데이터 위치로 memory를 지정하여 선언합니다. 저장소 배열과 마찬가지로 메모리 배열도 컴파일 시점에 고정 길이 또는 동적 길이를 가질 수 있지만, 메모리가 할당된 후에는 크기를 조정할 수 없습니다. 따라서 pop()과 pull() Solidity 함수를 사용할 수 없습니다.

고정 크기 메모리 배열은 uint256[5] memory numbers과 같은 문장으로 선언하면 자동으로 할당됩니다. 하지만 동적 메모리 배열을 할당하려면 new 연산자를 사용해야 합니다. 예를 들어 uint256[] memory numbers = new uint256[](5)``;와 같이 사용할 수 있습니다.

참고: 유효한 주소를 얻어 사용할 수 있도록 배열을 사용하기 전에는 항상 초기화하세요.

Solidity 배열 vs. 매핑

Solidity의 매핑은 데이터 그룹을 저장하기 위한 참조 타입이라는 점에서 배열과 유사합니다. 하지만 매핑의 문법과 구조는 상당히 다르며, 이로 인해 고유하고 중요한 목적을 수행할 수 있습니다. 매핑은 키와 값의 테이블로, 각각 사전에 정의된 타입을 가집니다. 매핑은 Solidity 스마트 컨트랙트 내부에 존재하며 데이터로 채워지기를 기다리는 빈 테이블을 생성하는 것으로 볼 수 있습니다.

배열과 달리, 매핑은 조회 가능한 길이를 가지지 않으며 키나 값이 초기화 단계에서 '설정'될 필요도 없습니다. 또한 Solidity 배열과 달리 매핑을 순회하는 것은 불가능합니다.

하지만 매핑에서 데이터를 가져오는 것은 배열에서 동일한 데이터를 가져오는 것보다 훨씬 효율적입니다. 배열에서 데이터를 가져오려면 원하는 특정 요소를 찾을 때까지 전체 배열을 반복해야 하지만, 매핑은 해당 데이터를 즉시 가져옵니다.

매핑에서 데이터를 가져올 때의 성능상 이점은 Ethereum 스마트 컨트랙트에서 가스를 절약하는 데 상당히 중요할 수 있는데, 블록체인 상의 데이터를 수정하는 트랜잭션은 가스 수수료를 필요로 하기 때문입니다. 따라서 스마트 컨트랙트에서 데이터를 최대한 효율적으로 저장하거나 가져오는 것은 시간이 지남에 따라 개발자의 ETH를 절약해줄 수 있습니다.

요약하면, for 루프 같은 것을 사용해 데이터 그룹을 순회해야 한다면 배열을 사용하세요. 데이터 집합을 순회할 필요가 없고 알려진 키를 기반으로 값을 가져올 수 있다면 매핑 사용을 고려하세요.

배열 선언 및 초기화

Solidity에서 배열을 선언하는 것은 비교적 간단합니다. 요소의 데이터 타입과 요소의 개수를 지정해야 하며, 배열의 크기는 양의 정수여야 하고, 데이터 타입은 Solidity에서 유효해야 합니다.

예를 들어, 아래의 기본적인 명령은 배열을 초기화하며 데이터가 삽입되면 console.log에 표시됩니다.

배열의 각 항목은 요소라고 부르며, 각 요소는 "n - 1" 함수를 통해 숫자 인덱스로 접근할 수 있습니다. 여기서 n은 요소 번호를 나타냅니다.

일반적으로 요소와 관련된 번호 매김은 0부터 시작합니다. 이는 10번째 요소는 인덱스 9에서, 11번째 요소는 인덱스 10에서 접근할 수 있다는 것을 의미합니다.

동일한 데이터 타입과 관련된 제약은 기억하는 것이 중요한데, 배열은 연속된 메모리 셀에 저장되므로 모든 셀이 동일한 타입이어야 하고 따라서 동일한 크기여야 하기 때문입니다.

구조체(Structs)

구조체는 프로그래머가 자신만의 데이터 타입을 정의할 수 있게 해줍니다. 구조체가 정의되면 상태 변수로 사용하거나 위치 인수 또는 키워드를 사용하는 다양한 함수에서 사용할 수 있습니다. 두 번째 방식은 구조체 안에 포함된 멤버들의 순서를 기억해야 하는 상황을 피할 수 있게 해줍니다.

다음은 새로운 구조체의 예시입니다:

매핑(Mappings)

매핑은 모든 가능한 키가 존재하며 키를 사용해 한 번의 동작으로 값을 설정하거나 가져올 수 있는 키-값 저장소로 생각할 수 있습니다. KeyType은 built-in 값 타입(예: bytes, string 또는 컨트랙트/열거형(enum) 타입)이 될 수 있습니다. ValueType은 매핑, 배열, 구조체를 포함한 어떤 타입도 될 수 있습니다.

매핑은 다음과 같이 선언할 수 있습니다:

참고: 매핑 변수에 허용되는 유일한 데이터 위치는 storage입니다.

멤버(Members)

배열의 특정 멤버를 찾는다는 것은 해당 멤버를 찾을 때까지 배열을 검색하는 것을 의미합니다. 개발자는 lengthpush라는 두 가지 중요한 함수를 알아두어야 합니다.

  • Length - 배열의 크기를 반환하며 동적 배열의 크기를 변경하는 데도 사용할 수 있습니다.
  • Push - 개발자가 동적 저장소 배열의 끝에 요소를 직접 추가할 수 있게 해주며, 그 결과 배열의 새로운 길이를 반환합니다.

Solidity 배열 트러블슈팅

개발자는 때때로 컴파일 오류를 마주칠 수 있는데, 이는 배열의 선언, 생성, 초기화 규칙에 대한 오해에서 비롯될 수 있습니다. Solidity 배열의 일반적인 오류를 해결하는 몇 가지 상위 수준의 방법은 다음과 같습니다:

1. Array.length

array.length 함수를 사용하면 개발자는 배열에 존재하는 요소의 개수를 확인할 수 있습니다. 메모리 배열의 크기는 선언 시 고정되는 반면, 동적 배열은 런타임에 정의되므로 조작을 위해서는 length가 필요합니다.

2. Abi.encode와 abi.encodePacked

외부 컨트랙트에서 함수가 호출되면 EVM은 함수 시그니처와 인수를 포함하는 bytes 버퍼를 계산하며, 인수를 직렬화하는 방법에는 abi.encodeabi.encodePacked, 두 가지가 있습니다.

Abi.encode는 ABI 명세를 사용하여 매개변수를 인코딩합니다. 사실상 ABI는 컨트랙트 호출을 위해 설계되었으며, 매개변수는 32바이트로 패딩됩니다. 컨트랙트에 호출이 이루어지고 있다면, 이 함수가 사용되고 있을 가능성이 높습니다.

이에 비해 abi.encodePacked는 해당 타입에 필요한 최소한의 공간을 사용하여 매개변수를 인코딩합니다. unit8을 인코딩하면 1바이트를 사용하며, 이는 공간을 절약하고 컨트랙트를 호출할 의도가 없을 때 사용됩니다.

3. Byte[] 또는 bytes

두 배열 타입 모두 임의 길이의 원시 바이트 데이터를 담을 수 있습니다. byte\[\] 과 bytes의 차이점은 bytes\[\]는 배열 타입의 규칙을 따르며, Solidity의 메모리 배열 요소는 항상 32바이트의 배수를 차지한다는 것입니다. 즉, 요소가 32바이트의 배수보다 작으면 필요한 크기에 맞춰질 때까지 패딩됩니다. 예를 들어 byte 배열의 경우 요소당 최대 31바이트가 낭비될 수 있는데, 이는 bytes 배열이나 string을 사용할 때는 발생하지 않습니다.

4. String

이 특정 동적 배열은 UTF-8 데이터 타입이며, 다른 프로그래밍 언어와 비교했을 때 기능적으로 다릅니다. Solidity는 문자열의 길이를 가져오거나 두 문자열을 연결하거나 비교하는 함수를 제공하지 않습니다. 문자열은 bytes[]를 사용해 바이트 배열로 빠르게 변환할 수 있습니다.

5. Solidity array null

Python이나 JavaScript 같은 다른 프로그래밍 언어에는 undefined, null, nil, none 등의 고유한 개념이 존재하지만, Solidity에는 존재하지 않습니다. 대신 개발자는 이를 zero 또는 default value 개념이라고 부를 수 있는데, 각 값이 생성되는 순간 메모리에 슬롯을 할당받으므로 반드시 무언가를 담고 있어야 하기 때문입니다.

Solidity 배열에 대해 더 알아보기

Solidity 프로그래밍 언어에 대해 더 자세히 알아보고 배열을 비롯한 다른 기초적인 주제들에 익숙해지려면 Alchemy University의 Ethereum bootcamp를 확인해보세요. Ethereum bootcamp는 가이드 영상과 실습 프로젝트로 구성된 무료 7주 프로그래밍 과정입니다.

개발이 전반적으로 처음인 개발자라면, Alchemy의 3주 JavaScript for Ethereum 과정이 Ethereum bootcamp를 시작하기 전에 좋은 선행 과정이 될 것입니다.

Background gradient

블록체인 매직을 만드세요

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