Post

포인터로 메모리를 직접 다루어 보기

자바 개발자로 일하다 보면, JVM이 메모리 개발자 대신 관리해 준다. 그래서 메모리에대해 깊게 고민해볼 기회가 많지 않다. 하지만 메모리 관리는 개발자라면 반드시 알고가야할 부분이다.

최근 C언어를 학습하며 컴퓨터의 동작방식에 대해 깊게 공부하려고 노력 중이다. (사실 학교다닐때 열심히 공부했어야 했던것들..) 오늘은 메모리 동작방식에 대해 정리해 보았다.

메모리

메모리는 컴퓨터가 프로그램을 실행하고 데이터를 저장하는 데 사용하는 장치 이다. 메모리는 물리적인 RAM(랜덤 액세스 메모리)과 가상 메모리로 나눌 수 있다.

가상 메모리

일반 PC에 사용되는 메모리의 용량은 생각보다 크지 않다. 평균적으로 8GB ~ 16GB 정도라고 할 수 있겠다. 그런데 우리가 사용하는 프로그램들의 용량을 생각해 보자 컴퓨터에는 기본적으로 동작하는 수많은 프로세서들이 있다. 더불어 사용자가 엑셀을 띄워높고, 크롬텝 여러개를 생성해서 ppt를 만든다고 가정해 보자. 모르긴해도 상당한 메모리 공간이 필요할 것이다. 물리적인 메모리용량 만으로는 이 모든 프로그램들을 안정적으로 운용하기 쉽지 않다. 그래서 등장한 개념이 바로 가상 메모리 이다.

물리적인 메모리공간 뿐만 아니라 디스크(SSD, HDD)의 까지 마치 메모리인것처럼 추상화하여 사용하는 것이다. 메모리가 할당되는 순서는 다음과 같다.

  1. 프로세스가 OS에게 메모리공간을 요청한다.
  2. OS는 프로세스에게 가상메모리 주소를 전달한다.
  3. 프로세스는 전달받은 주소를 가지고 원하는 동작을 수행한다.
  4. 작업이 완료되면 프로세스는 할당받은 메모리를 OS에 반환 한다.

위의 순서에서 프로세스는 이 공간이 실제 메모리 영역인지, 디스크 영역인지 상관하지 않는다. 단지 하나의 큰 가상 메모리로 인식할 뿐이다. OS는 디스크,메모리를 활용하여 추상화된 가상메모리를 만들어 내는 것이다. 프로세스 입장에선 그냥 주소만 알면 그만인 것이다. 응집도가 높다고 볼 수 있겠다.

포인터

포인터는 C / C++ 에서 지원하는 문법인데 메모리의 주소를 개발자가 직접 다룰 수 있는 아주 강력한 방법이다. 포인터를 이용해 메모리에 직접적으로 접근할 수 있는 만큼 개발자에게 높은 자유가 주어진다. 잘 사용하면 아주빠른 성능을 낼 수 있겠으나, 메모리를 잘못건드린 모든 개나는 개발자의 몫이다. 높은 자유만큼 높은 책임도 동시에 주어지는 것이다.

이런면에서 메모리를 대신 관리해주는 Java가 협업을 하기엔 좀더 안정적이라 생각한다. 컴퓨터의 세계는 모든것이 선택과 집중이다. 다시 돌아와서 포인터는 포인터는 메모리 주소를 저장하는 변수로, 다른 변수나 데이터 구조체의 위치를 가리키는 역할을 한다. 이를 통해 변수의 값을 변경하거나 참조할 수 있다. 아래의 코드를 한번 보자

1
2
3
4
5
6
7
8
int main() {
    int num = 10;
    int * ptr = #
    
    printf("변수 num의 값: %d\n", num);
    printf("포인터 ptr이 가리키는 값: %d\n", *ptr);
    return 0;
}
  • int num = 10;
    • num이라는 변수에 10을 할당하는 코드 이다. num변수의 메모리 주소는 OS에 의해 결정 된다. memory-image1
    • 위의 그림을 보면 0x00007ff7bb85b428 주소를 기준으로 4Byte 만큼 메모리에 공간(Stack 영역)이 할당된 것을 확인할 수 있다. num의 값에 접근하기 위해서는 0x00007ff7bb85b428 주소값이 필요한 것이다.
  • int * ptr = #
    • 그렇다면 포인터란 무엇인가, 가리킨다는 뜻이다. 무엇을? 주소값을, 즉 메모리의 주소값을 가리키는 것이라 생각하면 된다. memory-image2
    • 위의 그림을 통해 ptr에 어떤값이 저장되어있는지 확인해 보자 8Byte 공간이 할당 되었고 정확히 0x00007ff7bb85b428(num의 주소값)이 저장되어 있는것을 확인할 수 있다.
    • 흥미로운점은 주소값이 1Byte씩 저장되는데 메모리의 가장 뒤쪽부터 저장된다는 것이다. 나의 PC는 인텔 칩을 사용하는데 인텔은 리틀엔디안 (메모리의 가장 낮은 바이트 부터 데이터를 저장하는 방식) 방식을 따르기 때문에 그렇다.

주소값을 저장하는 공간에 8Byte가 할당되는 이유

변수를 사용하면 변수의 크기만큼 메모리에 공간이 할당된다. 아래는 각 타입별 크기를 정리한 것이다.

타입크기
char1 Byte
short2 Byte
int4 Byte
long4 Byte(32bit) 또는 8 Byte(64bit)
long long8 Byte
float4 Byte
double8 Byte
long double10 Byte 이상

그렇다면 메모리공간에, 메모리 주소값을 저장하려면 얼만큼의 공간이 필요할까? 그것은 CPU가 몇 bit 프로세서인지에 따라 달라 진다.. 요즘 대부분의 CPU는 64bit를 지원한다. 64bit 프로세서는 데이터의 주소, 레지스터 및 데이터 버스의 너비가 64bit인 프로세서를 말한다. 즉 메모리의 주소값을 64bit값으로 다루겠다는 것이다. 처리한다는 말이다. 따라서 이경우 메모리 주소값을 저장하기 위해선 64bit(8Byte)의 메모리 공간이 필요한 것이다.

  • 64bit 시스템은 1Byte 메모리에 부여하는 주소가 64비트 상수인 것이다. memory-image3

결론

오랫동안 자바를 사용하면서 애써 외면해 왔던것들을 다시 펼쳤다. 실력있는 개발자가 되기위해 반드시 짚고 넘어가야할 부분이라 생각한다. 컴퓨터의 동작 원리에대해 앞으로 좀더 깊게 공부해 보아야 겠다. 한가지 드는 의문은 Java 의 GC는 어떤식으로 메모리를 관리하는지 궁금하기도 하다. C언어에서도 나만의 GC를 만들어서 사용할 수 있지 않을까 하는 생각이 든다.

This post is licensed under CC BY 4.0 by the author.