나는 Windows 10 UWP앱을 C++/CX로 개발하고 있다.
그런데 난 STL을 잘 사용하지 않는다. 아주 가끔 사용한다. 정말 노가다가 귀찮을때만 쓴다.
오늘 UWP앱으로 테스트할게 있어서 몇 줄 코드를 작성했다.
그런데 난 STL을 잘 사용하지 않는다. 아주 가끔 사용한다. 정말 노가다가 귀찮을때만 쓴다.
오늘 UWP앱으로 테스트할게 있어서 몇 줄 코드를 작성했다.
노가다가 귀찮아서 stl::queue를 사용했다. 무심코 ref new로 할당한 ref class 객체(이하 ^객체-hat object로 표시함)를 여기 집어넣으면 ref count가 증가할까? 라는 의문이 들었다. 테스트를 해보니 놀랍게도 ref count 자동으로 관리되었다. queue에 push하면 ref count 1증가, pop하면 ref count가 1 감소했다.
C++/CX에서의 ^객체는 내부적으로는 IInspectable인터페이스를 가지는 COM객체이다. 내부적으로 AddRef()와 Release()메소드를 가지고 있지만 바깥으로 노출하진 않는다.
개념적으로는 스마트 포인터를 가지고 있지만 이 역시 C++코드로는 확인할 수 없고 컴파일 후에 어셈블리 코드를 보면 확실히 작동하는 것을 알 수 있다.
C++/CX에서 지원하는 컬렉션을 사용할때는 당연히 레퍼런스 카운트가 자동(컴파일타임)으로 관리된다. 컬렉션에 집어넣으면 카운트가 증가하고 빼면 카운트가 감소한다.
그런데 STL에 그런 배려가 있던가? 일단 표준적인 STL에서 C++/CX니 COM이니 따로 고려해줄 이유가 없다.
개념적으로는 스마트 포인터를 가지고 있지만 이 역시 C++코드로는 확인할 수 없고 컴파일 후에 어셈블리 코드를 보면 확실히 작동하는 것을 알 수 있다.
C++/CX에서 지원하는 컬렉션을 사용할때는 당연히 레퍼런스 카운트가 자동(컴파일타임)으로 관리된다. 컬렉션에 집어넣으면 카운트가 증가하고 빼면 카운트가 감소한다.
그런데 STL에 그런 배려가 있던가? 일단 표준적인 STL에서 C++/CX니 COM이니 따로 고려해줄 이유가 없다.
어떻게 STL에서 ^객체의 레퍼런스 카운트를 조정하는지 궁금했다.
다시 간단한 샘플코드를 작성해서 테스트를 시작했다.
stl::vector에 ^객체를 넣고 ref count 변화를 본다. 그리고 코드를 따라가본다.
여기서 ^객체의 ref count를 보는 방법에 대해 참고.
^객체의 레퍼런스 카운트를 직접 얻을 수는 없지만 약간의 편법을 사용해서 레퍼런스 카운트를 얻을 수 있다.
^객체는 IUnknown으로 타입캐스팅을 하면 레퍼런스 카운트를 수동으로 관리할 수 있다. 물론 권장되는 방법은 아니지만 디버그용으로 사용하면 좋다.
^객체는 IUnknown으로 타입캐스팅을 하면 레퍼런스 카운트를 수동으로 관리할 수 있다. 물론 권장되는 방법은 아니지만 디버그용으로 사용하면 좋다.
일단 테스트 소스코드는 다음과 같다
복잡한 코드들을 한라인씩 따라가는건 무척 답답한 일이므로 ref count를 조정하는 코드를 바로 찾기로 했다.
Visual Studio의 막강한 데이터 브레이크 기능을 이용하면 메모리의 변화를 쉽게 찾을 수 있다.
우선 ^객체에서 ref count가 저장되는 메모리 위치를 찾았다. x86기준으로 ref count는 ^객체의 베이스포인터-16바이트 주소의 4바이트에 저장된다. Windows 8/8.1에선 베이스 포인터+12바이트 주소였는데 이 부분이 Windows 10 UWP에서 바뀌었다.
VS 디버거에서 이 어드레스의 4바이트 값이 변경되는 시점에 브레이크를 걸도록 데이터 브레이크를 설정한다.
금방 코드가 나왔다.
스샷과 같이 ^객체의 베이스가 되는 Platform::Object^ 객체를 __abi_IUnknown*타입으로 캐스팅해서 __abi_AddRef()를 호출하고 있다.
콜스택을 좀더 거슬러 올라가보면 다음과 같은 코드가 나온다.
콜스택을 좀더 거슬러 올라가보면 다음과 같은 코드가 나온다.
C++코드로는 타입에 따라서 뭘 따로 하지는 않는다. 원소를 받아서 new로 복사본을 만들고 그걸 삽입하는걸로 보인다.(코드를 완전히 이해하지는 못했다.)
여기서 new로 메모리를 할당하는데 입력받은 타입의 오버로딩된 new를 호출한다.
Platform::Object^의 new로 진입하면 새로 메모리를 할당하고 그 주소를 리턴하는 것이 아니라 자기자신의 ref count를 증가시키고 __abi_IUnknown 포인터터로 캐스팅해서 리턴하고 있다.
Platform::Object^타입이 아닌 경우는 그냥 new 해당 타입의 메모리를 할당하고 리턴한다.
Platform::Object^타입이 아닌 경우는 그냥 new 해당 타입의 메모리를 할당하고 리턴한다.
<결론>
1. C++/CX를 STL과 함께 사용할 경우 ^객체의 특성은 그대로 적용된다. ref count관리에 신경쓸 필요 없다.
2. STL코드 자체는 ^객체를 따로 구분해서 처리하지는 않는다.
3. ^객체의 ref count관리는 new를 오버로딩해서 처리한다. ref count관리가 아닌 타입이라면 new는 그냥 새로운 메모리를 리턴한다. ^객체의 경우 ref count를 증가시키고 IUnkown포인터를 리턴한다.
1. C++/CX를 STL과 함께 사용할 경우 ^객체의 특성은 그대로 적용된다. ref count관리에 신경쓸 필요 없다.
2. STL코드 자체는 ^객체를 따로 구분해서 처리하지는 않는다.
3. ^객체의 ref count관리는 new를 오버로딩해서 처리한다. ref count관리가 아닌 타입이라면 new는 그냥 새로운 메모리를 리턴한다. ^객체의 경우 ref count를 증가시키고 IUnkown포인터를 리턴한다.