Adaid's Workroom

[세가] 팁:포인터와 메모리 본문

서적 공부/게임 프로그래밍의 정석

[세가] 팁:포인터와 메모리

어데이드 2017. 12. 20. 18:54

모든 변수는 거대한 배열인 메모리에 배치되어 첨자로 관리된다(memory[2]과 비슷한 식으로).

컴퓨터에서 메모리는 결국 char형 배열에 지나지 않는다.

변수와 배열, 구조체, 함수, 클래스 등은 모두 메모리 위에 놓여져 '몇 번 부터 몇 번까지는 어떤 변수'라는 표로 관리된다.

이 표를 임의로 만들어 주는 것이 컴파일러다.

포인터

이 메모리배열에서 첨자를 메모리 주소(memory address) 혹은 단순히 주소(address)라고 부른다.

그리고 이 첨자가 들어 있는 변수를 포인터(pointer)라고 부른다.

C++ 포인터에서 사용하는 * 연산자는 포인터 변수를 첨자로 하여 메모리 배열에 접근하는 것이다.

모든 변수는 단순히 메모리 char 배열 중 어딘가를 빌려서 자리잡고 있는 것에 지나지 않는다.

예를 들어 클래스는 private 변수로 향하는 접근을 막고 있지만 

마침 그곳 주소 즉 첨자를 알고 있어서 그 장소로 접근하는 포인터를 만든다면 그 변수를 조작할 수 있다.

포인터와 배열

char a[3];

위의 문이 하는 일은 메모리 안에서 비어 있는 장소를 찾아내서 그 주소를 a변수에 넣는 것이다.

주소가 있는 변수를 포인터라고 하므로 배열변수는 포인터 이외 아무것도 아니다.


char* b;

배열과 포인터 사이에 차이는 포인터는 비어 있는 메모리를 찾아 첨자를 넣는 처리를 생략한다는 것 뿐이다.

그러므로 b에는 사용할 수 있는 첨자가 들어있지 않아서 나중에 어떤 값을 대입해야만 한다.

다시 말해 배열변수는 '처음부터 사용 가능한 첨자가 들어 있는 포인터'를 말한다.

0포인터

포인터에는 '값이 0일 때는 아무것도 하지 않는다'는 규칙이 있다.
메모리 배열에서 0번만은 사용하면 안 된다고 정해져 있는 것이다.


int* p = 0;

*p = 4;        //쓰기 오류

int a = *p;    //읽기 오류


따라서 위의 코드는 규칙을 위반해 프로그램이 멈춘다.

delete한 포인터에 0을 넣는 이유는 이런 성질을 디버그에 활용하려는 것이다.

delete해도 포인터에는 값이 남아있는데 그 값을 이용하면 메모리에 접근할 수 있다.

이를 방지하기 위해 포인터에 0을 넣는 것이다.


또한 이런 특별한 규칙이 있기 때문에 상황에 따라서 0은 정수로 해석되기도 하고 포인트로 해석되기도 한다.

이 성질은 0만이 가진 특성이다.


int* p = 0;    //가능

int* p = 1;    //불가


그러므로 위의 문은 가능하나 아래는 아니다.


이 사양 때문에 곤란한 상황이 생길 수 있다.

A(int) 정수를 받는 생성자와 다른 형의 포인터로 만드는 생성자 A(B*)가 있으면 곤란해진다.

이때 A(0)이라고 쓰면 어느 쪽인지 알 수 없어 생성자가 오류를 낸다.

함수라면 이름을 바꾸면 그만이지만 생성자는 그럴수 없기 때문에 어느 한쪽은 포기해야 한다.

포인터 형

포인터의 형 이름은 컴파일러가 다른 형의 포인터끼리 복사하지 않게 검사하는 기호에 지나지 않으며,
그 알맹이는 전부 메모리 배열의 첨자다.
단 포인터에 숫자를 더할 때는 조금 특별한 일이 일어난다.

int* ip;
char* cp;

예를 들어 포인터가 이렇게 정의되어 있다고 할 때 첨자에 0을 넣어 다음과 같이 배열로 나타내보자.

ip[0];
cp[0];

이렇게 쓰면 포인터가 가리키는 장소에 있는 내용을 가져올 수 있다.
그리고 다음과 같이 첨자에 1을 넣으면 포인터가 배열이 되었다고 가정하고 다음 요소를 가져올 수 있다.
하지만 char은 1바이트, int는 4바이트이므로 다음 요소까지 거리도 1과 4로 다르다

*(ip + 1)
이것이 ip[1]과 같은 표현이 되게 하려면 +1이 'int형 한 개 크기만큼 더한다'는 의미가 될 필요가 있고
실제로 내부에선 4가 증가한다.

T* p;
p +=2;

와 같은 코드가 있을 때 내부적으로 다음과 같이 동작한다.

address += sizeof(T) * 2

이런 구조를 통해 배열의 첨자와 포인터 첨자를 더하는 관계를 간단히 하고 있다.

new와 delete

new에는 두 가지 역할이 있다.
1. 메모리에서 비어 있는 장소를 찾아내 그 첨자를 포인터에 넣음
2. 그 장소에 대해 생성자 호출
delete는 생성자와는 반대로 소멸자를 호출한 뒤 매모리를 해제한다.

new에서 메모리 확보 기능은 '비어있는 장소에 있는 첨자를 반환하는 것'이다.
비어 있는지는 new 안에서 동작하는 메모리 관리 프로그램이 조사한다.

배열과 new의 차이

int p[5];                    //배열
int* p = new int[5];     //new

C++배열에는 new에는 없는 제한이 있다.
1. 필요한 메모리 크기를 컴파일할 때 알고 있을 것
2. 함수가 끝날 때 반드시 해제할 것

이런 제한이 있으면 메모리에서 빈공간을 찾아오는 작업이 매우 편해진다.
반대로, 이런 제한이 없으면 메모리에서 빈 공간을 찾는 데 걸리는 시간이 길어진다.

new와 달리 배열은 빈 곳을 찾는 작업을 컴파일할 때 끝낸다.
컴파일을 마쳤을 때는 이미 첨자가 모두 확보되어 있으므로 실행 중 낭비되는 처이가 전혀 발행하지 않는다.

이런 차이가 있으나 양쪽 모두 메모리에서 빈 곳을 찾아 그 첨자를 가져오는 것은 동일하다.


'서적 공부 > 게임 프로그래밍의 정석' 카테고리의 다른 글

[세가] 팁:참조  (0) 2017.12.22
[세가] 팁:플래그  (0) 2017.12.19
[세가] C++ 보충 (2)  (0) 2017.12.19
[세가] C++ 보충 (1)  (0) 2017.12.17
Comments