포인터

내위키

Pointer.

프로그래밍 언어에서 다른 변수(클래스를 비롯한 객체, 함수 등등도 포함한다)의 메모리 공간 주소를 가리키는 변수를 뜻한다.[1] C/C++ 언어의 강력한 기능이자 만악의 근원이며 프로그래머의 뒷목을 잡게 만드는 원흉이기도 하다.

포인터는 주로 C에서 많이 쓰이는 기능이기 때문에 여기서는 C를 기준으로 설명한다.

포인터는 데이터의 메모리 주소값만을 가지고 있기 때문에 포인터의 크기는 주소값의 크기로 고정된다. 예를 들어 32비트 프로그래밍을 한다고 가정하면, 그 '32비트'가 포인터의 크기가 된다. 즉 char든 double이든 float든 뭐든, 1만 바이트짜리 긴 문자열이든 그 포인터의 크기는 32비트다. 또한 포인터는 데이터의 시작 부분 주소값만을 담고 있다. 100 바이트짜리 char* 문자열이라고 해도 포인터는 무조건 첫 바이트의 주소값만 가지고 있다. 따라서 포인터만 가지고는 그 데이터가 어디에서 끝나는지 알 수 없다. C는 문자열의 끝이 NULL로 끝나야 한다고 정하고 있다.

포인터의 연산을 허용하는 언어의 경우, 포인터의 값을 변경시키면 포인터가 가리키는 주소가 바뀌는 결과가 된다. 예를 들어 포인터 변수에 1을 더하면 포인터가 가리키는 주소가 1 바이트 다음으로 옮겨 간다. 요즈음의 운영체제는 각 프로세스가 격리된 메모리 공간을 가지기 때문에 포인터 연산에 문제가 있더라도 그 프로세스가 죽어버리거나 하는 식으로 끝나지만 MS-DOS 혹은 그 위에서 돌아갔던 윈도우 3처럼 프로세스별 메모리 격리가 철저하지 않았던 운영체제는 포인터 연산을 잘못하면 다른 프로세스의 메모리를 침범하거나 심지어 운영체제가 사용하는 메모리 공간을 건드리는 사태가 벌어질 수도 있었다.

겉보기에는 포인터를 사용하지 않는 것처럼 보이는 프로그래밍 언어도 알고 보면 내부에는 포인터 개념을 사용하고 있는 모습을 볼 수 있다. 예를 들어 포인터와는 전혀 관련이 없을 것 같은 파이썬의 경우,

x = [1, 2, 3]
y = x

print(id(x), id(y))

이 코드는 y에 [1, 2, 3] 리스트를 대입한 것이 아니다. 이 코드를 실행시켜 보면 '4413603008'와 같은 결과가 나온다. 숫자는 그때 그때 달라지지만 두 숫자는 같은 값이 나온다. 즉 x, y 모두 같은 메모리 공간 주소를 가리킨다는 뜻이다. 즉 y에는 x의 메모리 공간 주소를 대입시킨 것이다. 따라서 y.append(4) 명령으로 리스트 y에 1을 추가시키면 x 역시 [1, 2, 3, 4]로 리스트의 내용이 바뀐다. 파이썬에서 클래스의 메서드를 정의할 때 반드시 첫 번째 매개변수로 self를 써 줘야 하는데, 실제 메서드를 부를 때에는 self는 쓰지 않는다.[2] 파이썬이 자동으로 클래스 인스턴스의 포인터를 넣어주기 때문이다. 단, 포인터 연산 같은 것은 막혀 있다. 포인터에 관련된 심각한 버그를 자주 일으키는 원인이 포인터다 보니 아예 포인터 연산을 막는 경우도 있지만 자바처럼 가상머신 위에서 돌아가는 언어는 물리적인 메모리 주소를 가지고 포인터 연산을 하는 게 의미가 없는 문제도 있기 때문.

포인터는 어려운 개념이고 C처럼 포인터 연산도 허용하면 여러 가지 버그를 일으키는 원인이 되지만 그래도 프로그래밍에서는 중요한 개념으로 쓰인다. 함수에 매개변수로 1 메가바이트짜리 데이터를 넘겨준다고 가정해 보자. 포인터가 없다면 데이터를 통째로 넘겨줘야 하지만 포인터를 쓰면 32 비트 프로그래밍이라면 32 비트짜리 데이터, 즉 메모리 주소만 넘겨주면 그만이다.[3]

각주[편집]

  1. 'Point'에는 '가리키다'라는 뜻이 있다. 즉 포인터는 point-er, '가리키는 것'이라는 뜻이 된다.
  2. 예를 들어 def func1(self, a)와 같은 식으로 정의하지만 부를 때에는 c.func('Hello')처럼 매개변수 a만 넘겨준다.
  3. 물론 이 경우 함수에서 데이터를 건드리면 당연히 함수를 호출한 쪽의 데이터도 똑같이 바뀐 상태가 된다. 이를 피하려면 함수 안에서는 읽기 전용으로만 쓰거나, 데이터를 복사해서 넘겨줘야 한다.