Swift 속의 C Pointer 이야기 - UnsafeBufferPointer, UnsafeMutableBufferPointer
버퍼(Buffer)라는 용어는 대체로 연속적인 메모리 공간을 의미한다. 메모리를 할당해서 구한 포인터는 이 버퍼의 시작 주소를 담고 있다고 볼 수 있다. 버퍼는 메모리 덩어리 그 자체다.
하지만 스위프트(Swift)는 포인터를 쓸 수 있는 언어가 아니기 때문에 연속적인 메모리를 액세스 하는 것이 불가능하다. 그래서 위의 버퍼 개념이 맞지 않는다.
Swift 에서는 버퍼를 대체하기 위해 배열(Array)을 대신 사용한다. 랜덤 액세스도 되고 이터레이션도 되는 그 배열 말이다.
UnsafeBufferPointer 와 UnsafeMutableBufferPointer 는 이런 Swift 버퍼와 C 버퍼 사이의 상호호환을 위해 제공되는 특수한 컨테이너다.
대신 아래와 같은 방법으로 UnsafeBufferPointer 를 거치는 방법을 이용하면 Swift Array의 포인터(UnsafePointer)를 구할 수 있다.
포인터에 대한 개념이 갖춰졌다면 아마도 어떤 현상이 발생할지 알 수 있는데, intArray 의 내용이 바뀌면 pointer 가 가리키는 메모리의 내용이 바뀐다.
그 다음에 bufferPointer 라는 버퍼포인터를 만든다. for 문을 통해서 이 버퍼포인터의 이터레이션을 하고 있는데 버퍼포인터 자체를 배열 처럼 쓸 수 있다는 말이기도 하다. 물론 타입은 버퍼포인터 이지만 말이다.
그 다음에 최종적으로 버퍼포인터를 배열(intArray)로 만든다.
물론 이 세 녀석들 - pointer, bufferPointer, intArray - 은 모두 같은 메모리를 참조하고 있다. 어느 하나가 바뀌면 나머지도 다 바뀐다.
아래 예제는 배열과 버퍼포인터 그리고 포인터와 생짜포인터 끼리의 변환에 관한 예제이다.
최종적으로 sourceArray와 destArray의 값을 비교하고 있는데 여기서는 같아야 정상이다. 물론 이 둘 즉 source와 dest에 연관이 있는 모든 것들은 물리적으로 다른 메모리이기 때문에 하나가 바뀌어도 다른 하나에는 영향이 없다.
[돌아가기] Swift 속의 C Pointer 이야기 - 시작
[관련글] Swift 속의 C Pointer 이야기 - UnsafePointer, UnsafeMutablePointer
[관련글] Swift 속의 C Pointer 이야기 - UnsafeRawPointer, UnsafeMutableRawPointer
[관련글] 스위프트(Swift) 가이드
하지만 스위프트(Swift)는 포인터를 쓸 수 있는 언어가 아니기 때문에 연속적인 메모리를 액세스 하는 것이 불가능하다. 그래서 위의 버퍼 개념이 맞지 않는다.
Swift 에서는 버퍼를 대체하기 위해 배열(Array)을 대신 사용한다. 랜덤 액세스도 되고 이터레이션도 되는 그 배열 말이다.
UnsafeBufferPointer 와 UnsafeMutableBufferPointer 는 이런 Swift 버퍼와 C 버퍼 사이의 상호호환을 위해 제공되는 특수한 컨테이너다.
배열을 버퍼포인터로, 버퍼포인터를 포인터로 바꾸기
그냥 UnsafeMutablePointer 를 원하는 크기로 만들면 사실 배열과 큰 차이는 없겠지만 그걸 Swift 의 Array로 바꾸는 것은 불가능하다. 왜냐하면 Array는 struct 로 구성된 타입이니까.대신 아래와 같은 방법으로 UnsafeBufferPointer 를 거치는 방법을 이용하면 Swift Array의 포인터(UnsafePointer)를 구할 수 있다.
var intArray = [1, 2, 3, 4, 5] let bufferPointer = UnsafeBufferPointer(start: &intArray, count: intArray.count) let pointer = bufferPointer.baseAddress! let dump: () -> () = { for i in 0..>intArray.count { print("\(pointer[i])") } } dump() intArray[0] = 6 dump()dump() 라는 클로져는 포인터가 가리키는 메모리의 내용을 찍어보기 위한 것이다. 그리고 마지막에 intArray의 값을 바꾸고 다시 dump()를 이용해 메모리의 내용을 찍어보고 있다.
포인터에 대한 개념이 갖춰졌다면 아마도 어떤 현상이 발생할지 알 수 있는데, intArray 의 내용이 바뀌면 pointer 가 가리키는 메모리의 내용이 바뀐다.
포인터를 버퍼포인터로, 버퍼포인터를 배열로 바꾸기
이번에는 반대 상황을 보자. 포인터를 버퍼 포인터로 바꾸는 것과 버퍼 포인터를 배열로 바꾸는 것이다. 아래 예제에 이 내용이 다 들어있다.let count = 5 let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count) defer { pointer.deallocate(capacity: count) } pointer[0] = 1 pointer[1] = 2 pointer[2] = 3 pointer[3] = 4 pointer[4] = 5 let bufferPointer = UnsafeMutableBufferPointer(start: pointer, count: count) for element in bufferPointer { print("\(element)") } let intArray = [Int](bufferPointer) print("\(intArray)")포인터를 만큼 Int 타입 5개의 크기로 메모리를 할당하고 여기다 데이터를 써 넣는다. 이 상태는 그저 메모리 덩어리일 뿐이다.
그 다음에 bufferPointer 라는 버퍼포인터를 만든다. for 문을 통해서 이 버퍼포인터의 이터레이션을 하고 있는데 버퍼포인터 자체를 배열 처럼 쓸 수 있다는 말이기도 하다. 물론 타입은 버퍼포인터 이지만 말이다.
그 다음에 최종적으로 버퍼포인터를 배열(intArray)로 만든다.
물론 이 세 녀석들 - pointer, bufferPointer, intArray - 은 모두 같은 메모리를 참조하고 있다. 어느 하나가 바뀌면 나머지도 다 바뀐다.
생짜포인터와 버퍼포인터와 배열
타입이 없는 포인터, 즉 생짜포인터(Raw Pointer)와의 상호 변환도 가능하다. 물론 바로는 안되고 일반포인터(UnsafePointer) 상태를 거치지만 말이다.아래 예제는 배열과 버퍼포인터 그리고 포인터와 생짜포인터 끼리의 변환에 관한 예제이다.
var sourceArray = [0, 1, 2, 3, 4, 5, 6, 7] let bytes = MemoryLayout<Int>.size // 8 let sourceBufferPointer = UnsafeBufferPointer(start: &sourceArray, count: sourceArray.count) let sourcePointer = sourceBufferPointer.baseAddress! let sourceRawPointer = UnsafeRawPointer(sourcePointer) let destRawPointer = UnsafeMutableRawPointer.allocate( bytes: MemoryLayout<Int>.size * 8, alignedTo: MemoryLayout<Int>.alignment) defer { destRawPointer.deallocate( bytes: MemoryLayout<Int>.size * 8, alignedTo: MemoryLayout<Int>.alignment) } let _ = memcpy(destRawPointer, sourceRawPointer, MemoryLayout<Int>.size * 8) let destPointer = destRawPointer.bindMemory(to: Int.self, capacity: 8) let destBufferPointer = UnsafeBufferPointer(start: destPointer, count: 8) let destArray = [Int](destBufferPointer) if sourceArray == destArray { print("Ok") }sourceArray 를 이용해 이를 생짜포인터화 시키는 것 까지의 과정, 그리고 생짜포인터를 memcpy()를 이용해 다른 메모리에 복사하는 방법, 그리고 복사받은 메모리의 생짜포인터를 destArray로 바꾸는 방법이 있다.
최종적으로 sourceArray와 destArray의 값을 비교하고 있는데 여기서는 같아야 정상이다. 물론 이 둘 즉 source와 dest에 연관이 있는 모든 것들은 물리적으로 다른 메모리이기 때문에 하나가 바뀌어도 다른 하나에는 영향이 없다.
[돌아가기] Swift 속의 C Pointer 이야기 - 시작
[관련글] Swift 속의 C Pointer 이야기 - UnsafePointer, UnsafeMutablePointer
[관련글] Swift 속의 C Pointer 이야기 - UnsafeRawPointer, UnsafeMutableRawPointer
[관련글] 스위프트(Swift) 가이드
댓글