2017년 1월 20일 금요일

Swift 속의 C Pointer 이야기 - 시작

Swift 에서 C 포인터(Pointer)는 왜 쓰는가. 모호할 때도 있고 쓰기도 귀찮고 문제도 자주 일으키는 그 개념을 말이다.

그런데 답은 C 포인터 라는 이름에서 이미 나와있다. 당연히 C로 구현된 함수가 포인터를 사용하게 되니 이 함수를 쓰려면 포인터를 다뤄야 한다는 것이다.

C 가 점점 대중(?)들에게 잊혀져 가는 현재로썬 포인터는 거의 쓸 일이 없어졌다고 볼 수도 있다. 하지만 동일개념을 사용하는 C++도 아직 현역이고 암호화 등 바이너리 연산 계통에선 여전히 C가 현역이다. 거기에 포인터도 거의 따라나니다 보니 아직은 땔 수 없는 애증(?)의 관계인 것 같다.

이번 글은 예전에 썼던 포인터 글이 오래된 것 같아 새롭게 Swift 3 기준으로 C 포인터에 대해서 작성해 본다.

Swift 에서 다루는 포인터의 종류

Swift 라는 언어 자체는 포인터를 지원하지 않는다. 물론 현대적인 언어 답게 레퍼런스 개념을 쓰긴 하지만 레퍼런스는 포인터와는 좀 다르다.

대신 Swift 에서는 포인터를 다룰 수 있는 컨테이너를 제공한다.

이런 포인터 컨테이너는 크게 두 부류로 나눌 있다.
  • 안전한 포인터
  • 위험한(?) 포인터

그런데 이 중 안전한 포인터는 사실 다룰 여지가 별로 없다. 대표적인 예로 OpaquePointer 같은게 있는데 그냥 정적 포인터이다. C 함수에서 별다른 실수를 하지 않는한 문제를 일으키지 않는다. 심지어 별로 쓸 일 조차 없었다 으어

그렇다면 반대로 위험한 포인터란 무엇인가. 이름에서 풍기는 그 위험함이 꺼림칙하게 만든다. 그런데 사실 많이 위험하지는 않고 그냥 컨테이너 이름이 안안전한(Unsafe)로 시작한다. Dangerous 와 Unsafe 는 확실히 어감이 많이 다르지만 적당한 반대말을 모르겠다. -_-;

Unsafe 류 포인터 컨테이너에는 Swift 의 C 포인터 기능의 거의 모든 내용이 쏠려있다. 그런데 이 녀석도 크게 두 분류로 쪼갤 수 있다.
  • 타입이 있는 포인터
  • 타입이 없는 포인터

Swift 의 특징 중 정적 타이핑(Static Typed) 혹은 타입 안전(Type Safe)이 자주 언급된다. 이 말의 의미는 컴파일 타임에 형(type)이 확정되지 않으면 컴파일이 되지 않는다. 그런데 타입이 없는 포인터라는 말은 이 부분에서 모순이 될 지도 모른다.

하지만 C에서 포인터를 다룰 때는 타입이 없는 포인터(void *)를 자주 다룬다. 그 때문인지 이런 형식을 우회적으로 지원하기 위해 타입이 없는 포인터를 특수한 방법으로 지원한다.

약간 더 상세하게 살펴보자.

타입이 있는 포인터

타입이 있는 포인터 컨테이너는 아래가 대표적이다.
  • UnsafePointer
  • UnsafeMutablePointer
이 두 녀석은 제너릭을 이용해 단위 타입이 명확한 포인터를 다룰 때 사용한다. 만약 int * 즉 정수형 포인터를 다룬다면 아래와 같은 형식일 것이다.
  • UnsafePointer<Int>
  • UnsafeMutablePointer<Int>
위 두 타입은 모두 각 데이터가 Int 타입 하나 이상인 메모리를 다룰 수 있다. 그 메모리는 대형 버퍼일 수도 있고 정수형 하나 크기의 메모리일 수도 있다. C의 포인터 자체는 원래 사이즈 정보가 없다보니 원래의 타입이 뭔지 알 수 없는게 정상이다.

Mutable 이라는 이름이 붙은 컨테이너의 역활은 어느 정도 유추가 가능할 것이다. 이 녀석은 C 포인터와 함께 따라다니는 Allocation(alloc, malloc, calloc 등) 이나 Deallocation(free 등), Initialization(memset, bzero 등)등의 기능까지 가지고 있어서 광범위한 포인터 혹은 포인터 버퍼 연산을 다룰 수 있다.

자세한 내용은 아래 글을 참고하자:
[상세글] Swift 속의 C Pointer 이야기 - UnsafePointer, UnsafeMutablePointer

타입이 없는 포인터

타입이 없는(Untyped) 포인터 컨테이너는 아래가 대표적이다.
  • UnsafeRawPointer
  • UnsafeMutableRawPointer
이 두 타입의 특징은 명확한 단위 타입이 없는 대신 Memory Align 이라 불리우는 정보가 따라다닌다는 점이다. 이 Align 은 포인터가 가리키는 메모리가 어떤 크기로 구성되는지를 타입이 아닌 수치로 표현하기 위한 존재다.

C에서 종종 쓰이는 void * 즉 무형의 포인터를 안다면 쉽게 이해가 될 것이다. 이 포인터는 char * 로 형변환을 하면 바이트버퍼로 사용 할 수 있게 되고 int * 로 형변환을 하면 정수형 배열처럼 액세스가 가능하다. 물론 이런 행태가 현대적인 언어인 Swift 에서는 이해할 수 없는 행위겠지만 어쨌든 그렇게도 쓸 수 있다.

하지만 명확한 타입이 지정되지 않았다는 점 때문에 Swift 에서 생(Raw)포인터를 쓰는 것이 좀 귀찮을 수도 있다.

Mutable 이름이 붙은 것은 위의 타입이 있는 포인터에서 언급했던 것과 비슷하다. 즉 Allocation  과 Deallocation 혹은 메모리 내용 수정 등의 기능을 추가로 더 쓸 수 있다.

자세한 내용은 아래 글을 참고하자:
[상세글] Swift 속의 C Pointer 이야기 - UnsafeRawPointer, UnsafeMutableRawPointer

공통적인 포인터 연산

C 포인터는 메모리 주소(Address)를 가리킨다는 정의가 있다. 해당 주소에는 뭐가 있는지 볼 수 있고 거기다 뭘 넣을 수도 있다. 또한 해당 메모리 주소의 다음 주소나 이전 주소를 탐방하는 것이 가능하다.

이 이야기들은 포인터 연산에 관한 것인데 당연히 Swift 의 포인터 컨테이너 들에서도 비슷하게 사용 할 수 있는 것들이다.
  • 해당 포인터의 내용 읽기: let value = pointer.pointee
  • 해당 포인터에 내용 쓰기: pointer.pointee = value
  • 해당 포인터의 다음 포인터: pointer.successor()
  • 해당 포인터의 이전 포인터: pointer.predecessor()
  • 해당 포인터의 상대주소 포인터: pointer.advanced(by: n)
  • 해당 포인터의 상대주소의 값 읽기/쓰기: pointer[n] = value 혹은 let value = pointer[n]
이 중 내용 쓰기는 Mutable 한 타입만 가능하다는 것은 상식적인 이야기라고 생각한다.

물론 컨테이너에 따라 이런 기능들이 필요 없거나 혹은 제공되지 않거나 하기도 하니 절대적인 공통점이라고는 할 수 없다.

기타

직접적인 포인터를 다루는 것은 위의 것이면 충분하겠지만, Swift 에서 포인터를 다루기는 귀찮은 많큼 몇 가지 도구(?)가 제공된다. 아래 글의 링크들을 참고하자.

[상세글] Swift 속의 C Pointer 이야기 - UnsafeBufferPointer, UnsafeMutableBufferPointer
[상세글] Swift 속의 C Pointer 이야기 - 기타 도구들​

마무리

주로 Unsafe 류에 대한 것을 다루다보니 위험한 상황을 만들지 않을까 걱정되는데, 사실 C 포인터 자체가 원래 안전장치가 부실하다. 남의 메모리에 침범하거나 남의 메모리를 free 시키거나 혹은 free 을 잊어먹거나 등등 다양한 문제를 일으키던 원흉이라 좀 불안정한 것은 사실이다.

그러니 우리 모두 Swift 에서 불안정한 포인터를 써 보고 된통 당해보자. 와~~~ 는 아니고, C와는 다르게 안전장치가 제법 풍부한 편이다 보니 Unsafe 라는 이름에 현혹될 필요는 없을 것 같다.

[관련글] Swift 속의 C Pointer 이야기 - UnsafePointer, UnsafeMutablePointer
[관련글] Swift 속의 C Pointer 이야기 - UnsafeRawPointer, UnsafeMutableRawPointer
[관련글] Swift 속의 C Pointer 이야기 - UnsafeBufferPointer, UnsafeMutableBufferPointer
[관련글] Swift 속의 C Pointer 이야기 - 기타 도구들​
[관련글] Swift 와 C 포인터(Pointer) (옛날글)
[관련글] 스위프트(Swift) 가이드

댓글 없음 :