전 구글 엔지니어였던 유튜버 CS dojo가 소개한 배열과 메모리에 대한 유튜브를 번역한 글이다.
왜 배열 끝에 원소를 추가할 수 없을까
C나 Java에서는 배열을 아래와 같이 표현한다.
int sampleArray[5] = {2, 4, 6, 8, 10};
이 배열 끝에 2, 3을 추가하고 싶지만 그럴 수 없다. 왜 그런지 메모리 관점에서 살펴보자.
메모리가 뭘까?
컴퓨터에 데이터를 저장하는 방식은 크게 두 가지 메커니즘으로 나눌 수 있다. 첫 번째는 메모리(RAM), 두 번째는 하드디스크같은 스토리지다.
스토리지는 영구적이지만 메모리는 그렇지 않다. 맥북 전원을 끄면 하드 디스크에 저장된 데이터는 그대로 남아있지만, 메모리에 저장된 데이터는 사라진다. 문서를 작성하다가 완전히 날라가 본 경험을 한 번씩 해봤을 것이다. 이는 텍스트가 스토리지가 아닌 메모리에 저장됐기 때문이다. 저장 버튼을 누르면 텍스트가 파일로 스토리지에 저장되므로 컴퓨터가 기억하게 된다.
스토리지에 파일을 쓰고, 읽는 과정은 느리다. 물건이 필요할 때마다 캐비넷에서 꺼낸다고 생각해보면 매우 번거롭다. 자주 쓰는 물건이라면 차라리 책상 위에 올려놓을 것이다. 이처럼 메모리에서 데이터를 읽고 쓰는 건 스토리지에 비해 매우 빠르다. 모든 과정이 끝난다면 스토리지에 저장해 영구적으로 데이터를 저장할 수 있다.
메모리와 스토리지가 애플리케이션과 함께 작동하는 방식도 비슷하다. Chrome같은 애플리케이션은 스토리지에 저장되어 있고, 실행하면 메모리에 로드되어 빠르게 접근할 수 있다. 많은 애플리케이션을 동시에 실행하면 똑같이 각자의 메모리 공간에 로드될 것이다. 너무 많은 애플리케이션을 로드하면 컴퓨터가 느려지는 이유다.
맥 상단 애플 로고를 눌러 이 Mac에 관하여를 살펴보면 16GB 같은 메모리 크기를 볼 수 있다. 스토리지는 457GB같이 메모리에 비해 훨씬 크다. 이제 Activity Monitor를 열어 메모리 탭을 클릭해보자. Chrome이 현재 사용하고 있는 메모리 크기(456MB)를 볼 수 있다.
프로그래밍에서 메모리
이제 이 개념이 프로그래밍에 어떻게 연관되는지 살펴보자.
int a = 1;
C로 작성된 위 코드를 컴파일하고 실행하면 변수 a가 만들어지고, integer 1이 할당된다. 사실 a는 메모리 주소 공간에 대한 식별자다. integer 1 은 스토리지가 아닌 메모리에 저장된다. 컴퓨터가 꺼지면 a도 사라진다는 뜻이다.
그런데 어떻게 이 Integer가 메모리에 저장될까? 이걸 이해하기 위해서는 두 가지 개념을 알아야 한다.
Bit
integer가 컴퓨터에 저장될 때는 32개의 0과 1로 표현된다는 것이다.
- 숫자 1:
00000000 00000000 00000000 00000001 - 숫자 2:
00000000 00000000 00000000 00000010
100이든, 200이든, -223이든 마찬가지다. 32개의 범위에서 0과 1로 Integer를 표현할 수 있다. 이때 각각의 0과 1을 bit라고 한다.
Bytes
두 번째 개념은 메모리가 bytes의 긴 테이프라는 것이다.
bytes는 뭘까? bytes는 데이터의 작은 단위다. bytes는 10010101 또는 00100010처럼 8bit로 구성된다. 메모리는 이 8bit짜리 bytes가 긴 테이프처럼 늘어져있는 모습으로 생각할 수 있다. 우리는 데이터의 작은 단위인 bytes를 메모리 상에 저장하거나 가져올 수 있다. 2칸 만큼, 혹은 4칸 만큼 원하는 만큼 저장할 수 있다.
컴퓨터는 각각의 bytes에 대해 주소를 할당하는데 각 주소는 120, 121, 122, 123, 124, 125처럼 하나의 Integer를 통해 표현된다. 메모리에 bytes 단위로 주소를 매긴다는 뜻이다. 저 120같은 주소는 운영체제가 애플리케이션을 시작할 때 결정한다. 만약 우리의 애플리케이션이 120140까지 메모리 공간을 차지한다고 하면 Chrome은 160180를 차지하는 방식이다.
여기서 이해해야 하는 중요한 개념이 있다. 주소를 120으로 임의로 배정했지만 120, 121, 122처럼 연속적으로 붙어 있는 방식은 컴퓨터에 있는 실제 시스템과 완전히 똑같다는 것이다.
Integer는 4bytes
다시 돌아가서 Integer는 32bit로 표현된다고 했다.(경우에 따라 64bit로 표현될 수 있음) 그럼 이 Integer를 메모리에 저장하기 위해서는 4bytes의 공간이 필요하다는 얘기다.
int a = 1;
int b = 3;
이때 숫자 1(00000000 00000000 00000000 00000001)은 메모리의 120-123에 저장되고, 3(00000000 00000000 00000000 00000011)은 바로 옆인 124-127에 저장될 것이다.
Integer만 다뤘지만, 2.3, 5.6, -2.8 같은 decimals나 'a', 'w' 같은 characters도 필요한 bytes 수는 다르겠지만 정확히 같은 원리로 작동한다.
배열 속 Integer는 어떻게 저장될까
이제 하나의 Integer가 아니라, 배열 속 Integer가 메모리에 저장되는 과정을 살펴보자.
int a = 1;
int sampleArray[3] = {5, 3, 20};

1은 120-123에, sampleArray의 첫번째 원소인 5는 124-127, ... 차례대로 저장될 것이다.
그럼 처음의 질문으로 돌아가서 왜 이 배열에 2와 3을 추가할 수 없는 걸까?
2와 3을 추가하기 위해서는 총 8bytes의 공간이 필요하다. 이걸 설명하기 위해서는 메모리가 어떻게 작동하는지 더 정확히 알아야하지만, 간단하게 살펴보자.
int a = 1;
int sampleArray[3] = {5, 3, 20};
int c = 4;
만약 마지막 줄의 코드가 있다고 하면 어떨까? 4는 136-139에 저장될 것이다.
따라서 만약 배열에 2개의 숫자를 더하고 싶다면 메모리의 빈 공간에 아예 5만큼의 길이를 가지는 새로운 배열을 만들어야 한다. 이때 기존 배열의 원소를 하나씩 copy하고 추가 원소를 할당해야 할 것이다. 이걸 dynamically로 표현하는데 중요하지만 아직 모르더라도 괜찮다.
새로운 배열에 숫자 3개를 더 추가하고 싶다면, 또 새로운 배열을 만드는 과정을 반복해야 할 것이다. C언어에서 이 동적 할당을 담당하는 함수를 realloc이라고 한다. 이게 배열을 resize하는 전략 중 하나인데, 새로운 배열을 만들고 기존 배열은 제거하는 전략이 별로긴 하지만 실제로 꽤 자주 쓰인다.
여기서 가변 배열(Resizable Array) 전략이 등장한다. 공간이 부족할 때 필요한 만큼만 늘리는 것이 아니라, 10 -> 20 -> 40 -> 80으로 두 배 씩 길이를 늘려가며 여유 공간을 확보한다. 가변 배열은 프로그래밍 언어별로 다양하게 제공하는데 파이썬의 list, 자바의 ArrayList 등이 있다. 자바스크립트 배열은 기본적으로 가변 배열로 동작한다. 오늘 배운 내용을 뇌의 메모리 뿐만 아니라 스토리지에 잘 저장해놓자! 😀