이타인클럽

데이터 타입과 타입별 저장 위치

토픽 이타인클럽 > Blockchain > Blockchain 개발

오늘은 좀 헷갈리는 데이터의 저장위치에 대해서 정리해 보겠습니다.

 

보통의 코딩 언어들은 변수를 선언하면 스택이라든지, 힙이라던지 메모리에 저장되는 것이 기본입니다. 그러나 솔리디티에서는 어떤 변수들은 메모리가 아니라 파일 시스템에 저장됩니다.

메모리라 함은, 간단히는 컴퓨터의 RAM과 같은 메모리를 말합니다. 보통은 운영체제(OS)의 가상 메모리에 저장되겠지만, 쉽게 RAM과 같은 곳에 데이터가 저장되는게 일반적입니다. 그러나 솔리디티의 경우는 어떤 변수들은 메모리가 아니라 하드 디스크와 같은 파일 시스템에 저장됩니다.

위 내용은 아래 책에 자세히 나와 있는데, 번역이 영 매끄럽지 않습니다. 그래서 이해하기가 좀 쉽지 않더군요.

이더리움을 활용한 블록체인 프로젝트 구축

!주의! 책 내용에 번역이 잘못된 부분이 좀 있으니, 책의 원서를 참고하시거나 아래 사이트를 참고 하시면 되겠습니다.
https://solidity.readthedocs.io/en/develop/types.html


제가 이해한 내용을 정리해 보겠습니다.

먼저, 솔리디티에서 변수 타입의 종류는 크게 두가지로 나뉩니다.

  • 복합 데이터 타입 (Complex Types): 문자열, 구조체, 배열
  • 기본 타입 (Basic Types): Boolean, Integers 등

변수 타입이나 종류에 따라 기본적으로 저장되는 위치가 정해져 있습니다. 그래서 이 기본적인 위치를 모르거나, 제대로 위치 저장을 하지 않으면 컴파일 에러를 일으킵니다. 솔리디티 코딩을 본격적으로 하려면 꼭 알아두어야 하는 내용입니다. 사실 컴파일러의 에러 메시지를 잘 검토하면 해결할 수 있으니 큰 문제는 아니죠. 그렇지만 변수의 저장 위치가 "메모리"와 파일을 뜻하는 "스토리지" 두 가지라는 점은 반드시 아셔야 합니다.

변수의 종류별 기본 저장위치 입니다.

  • 스토리지: 상태 변수, 함수 내 로컬 변수
  • 메모리: 함수의 매개 변수, 함수의 리턴값,

여기서 배열이나 구조체와 같은 복합 데이터도 위와 같은 룰을 따릅니다. 즉 복합 데이터 변수가 함수의 매개 변수로 쓰이면 기본적으로 저장위치는 "메모리"가 됩니다. 그러나 이들 복합 데이터 타입은 지시어를 사용하여 저장 위치를 지정할 수 있습니다. 예를 들어 함수 내 로컬 변수는 기본적으로 스토리지에 저장됩니다만, 아래와 같이 지시어를 사용하여 메모리에 저장하도록 합니다.

contact Sample{
  function Sample{
    uint24[3] memory myArray3= [1, 2, 99999];
  }
} 

위의 코드를 보면 myArray3은 Sample이라는 생성자 함수 내에 선언된 로컬 변수입니다. 로컬 변수는 기본적으로 스토리지에 저장된다고 했습니다. 그러나 지시어인 memory가 쓰여서 저장위치가 스토리지가 아니라 메모리가 됩니다.

그럼 조금 더 복합 데이터 타입인 배열과 문자열 예를 들어서 변수의 저장위치를 살펴보겠습니다.

contract Sample{
  int[] myArray = [0, 0];
  string myString= "Solidity";

  function Sample(uint index, int value){
    myArray[index] = value;
    int[] myArray2 = myArray;
    uint24[3] memory myArray3= [1, 2, 9999];
    uint8[2] myArray4= [1,2];
    
    string myString2= myString;
    string memory myString3= "ABCDE";
    myString3= "XYZ";
    string myString4= "Example";
  }
}

상태 변수

원서의 번역서의 번역이 잘못된 부분이 있으므로, 책을 보신다면 주의를 기울여 주세요.
먼저 contract의 최상위단에 선언된 변수들을 상태변수라고 합니다. 즉 어떤 함수의 속한 변수가 아니라 컨트랙트에 속한 변수의 의미입니다. 마치 클래스의 멤버 변수와 같습니다. 위의 코드의 경우 int[] myArray = [0, 0];와 string myString= "Solidity";의 myArray와 myString이 상태 변수가 되겠습니다. 상태 변수의 기본 저장위치는 스토리지 입니다. 즉 메모리가 아니라 파일에 저장됩니다.

int[] myArray = [0, 0];은 동적 배열을 상태 변수로 선언한 것이며, 여기서 [0, 0]과 같이 배열 리터럴(literal)이 상태 변수에 나타나면 이 리터럴은 스토리지에 저장되고, 함수 내부에 나타나면 메모리에 저장됩니다. 따라서 이 코드의 경우는 상태 변수와 리터럴 모두 스토리지에 저장되므로, 별 문제가 없습니다.

로컬 변수

함수 내에 아래와 같이 선언된 것은 로컬 변수입니다. 즉 상태 변수가 아닌 것입니다.
uint24[3] memory myArray3= [1, 2, 9999];
로컬 변수의 경우의 기본 저장위치는 스토리지 입니다. 그리고 배열의 리터럴은 함수 내부에 나타나면 메모리에 저장된다고 했습니다. 그래서 위 경우는 메모리에 있는 값을 스토리지에 넣는 것이므로, 컴파일 에러가 발생해야 하는데, myArray3 변수에 memory지시어를 사용하여 저장 위치를 메모리로 변경시켰기 때문에 에러가 발생하지 않습니다.

하지만, 그 아래에 있는 아래와 같은 코드는 컴파일 에러가 발생합니다.
uint8[2] myArray4= [1,2];
왜냐하면, 메모리에 있는 값을 스토리지에 넣으려고 했기 때문입니다.

문자열

문자열도 배열과 동일합니다. 문자열이 상태 변수에 나타내면 스토리지에 저장됩니다. 아래 코드의 문자열 리터럴 "Solidity"은 스토리지에 저장됩니다.

contract Sample{
  int[] myArray = [0, 0];
  string myString= "Solidity";
  ...
}

그러나 아래 코드는 컴파일 에러가 발생합니다.
string myString4= "Example";

문자열 리터럴 "Example"은 함수 내부에 나타나므로 메모리에 저장됩니다. 그런데 myString4은 함수 내부의 로컬 변수이므로 스토리지에 저장됩니다. 따라서 메모리에 있는 값을 스토리지에 넣으려고 하기 때문에 아래와 같이 에러 메시지가 발생합니다.

Error: Type string memory is not implicitly convertible to expected type string storage pointer.

위 내용은 구조체에 경우도 동일하게 적용됩니다.

이처럼 변수의 기본 저장위치를 잘 고려하여 코딩을 해야 하는게 좀 번거롭습니다.


원서의 번역서의 번역이 잘못된 부분이 있으므로, 책을 보신다면 주의를 기울여 주세요.
한가지 중요한 것을 설명안했는데, 데이터 저장 위치가 다르다고 항상 컴파일 에러가 발생하는 것이 아닙니다. 아래와 같은 경우는 자동으로 데이터 사본을 만들거나 원본의 주소가 저장되거나 합니다. 이 때도 주의깊게 봐야 할 것이 변수가 기본 타입이나 복합 데이터 타입이냐 따라 다릅니다.

  • 스토리지에 저장된 기본 타입 변수와 메모리에 저장된 기본 타입 변수간의 대입은 복사본이 생성되어 대입됩니다. 그러나 메모리에 저장되어 있는 복합 데이터 타입을 메모리에 있는 다른 복합 데이터 타입 변수에 대입할 때는 복사본이 만들어지 않습니다.
  • 상태 변수는 항상 스토리지에 저장되는데, 상태 변수간의 대입은 항상 복사본을 생성하여 대입됩니다.
  • 메모리에 저장된 복합 데이터 타입의 값을 로컬 변수에 대입할 수 없습니다. 이 경우가 위에서 살펴본 배열과 문자열에서 컴파일 에러를 발생한 경우입니다. 즉 메모리에 있는 배열 리터럴, 문자열 리터럴을 로컬 변수에 대입하려 했기 때문에 컴파일 에러가 발생한 것입니다.
  • 상태 변수를 로컬 변수에 대입하는 경우는 복사본이 생성되는 것이 아니라, 로컬 변수가 상태 변수의 포인터가 됩니다. 따라서 로컬 변수를 이용하여 값을 변경하면, 상태 변수의 값도 변경되는 것이죠.

솔리디티 사이트를 방문하면 아래와 같은 문구가 나옵니다. 데이터 저장 위치가 "memory", "storage"외에 "calldata"라는 것이 존재합니다.

Function parameters (not return parameters) of external functions are forced to calldata and behave mostly like memory.

사실 함수의 매개변수(리턴값 제외)는 메모리가 아니라 "calldata"라는 저장위치에 저장된다고 쓰여 있습니다. 그러나 "calldata"는 "memory"와 거의 비슷하게 동작한다고 되어 있습니다. 그래서 책에서는 별도로 구별하지 않은 것 같습니다.


오늘 내용은 초보자들에게 상당히 어렵게 느껴질 거 같네요. 저도 책을 보면서 이해가 잘 안되어 원서 및 솔리디티 사이트를 오가며 이해를 했습니다. 틀린 내용은 바로 잡아 주세요~ 사실 이런 내용을 모르고 코딩을 하면 좋겠지요. 또 모르고 코딩해도 큰 문제는 없을 것입니다. 그러나 컴파일 에러가 발생했을 때, 왜 에러가 발생했는지 원인을 알면 에러를 줄이면서 코딩할 수 있겠죠.

어째 점점 더 어려운 내용이 나와서 진행하기가 쉽지 않네요. 그래도 공부차원에서 계속 힘내서 써보겠습니다.

오늘의 실습: 왜 솔리티디는 데이터 저장 위치를 메모리, 스토리지, (또 calldata)로 나눠 놓은 것일까요? (저도 모르겠네요. 블체인의 특성 때문일 것이라고 생각되는데...)

댓글

댓글 본문
  1. 이승훈
    안녕하세요. 일반적인 프로그래밍을 할때 변수 형 저장 방법에 대해 제가 이해가 된건지 확인하고 싶은게 있어서 댓글 남깁니다. 예를 들어 int, uint 형이 있다면 메모리, 디스크에 따로 저장 공간이 있어서 그곳에 변수를 저장하고 메모리를 읽을 때 그 공간 임을 확인해서 변수 타입을 알 수 있다는 애기인가요?
  2. Blade
    잘 읽었어요. Storage 변수는 하드 디스크에 기록된다는 게 아니고 블록체인 상에 저장된다고 하네요.
    그래서 가스비용이 발생하기 때문에 주의를 요한다고...