TL;DR
- 함수 지역
static변수는 실행이 선언에 처음 도달할 때 한 번만 초기화되며, 이후 호출에서도 같은 값을 공유함. - 수명 문제를 피하려고
BasicWidgetInfo를static으로 만들면 첫 호출의 플래그가 고정되어 모든Gadget의 위젯 옵션이 같아짐. - 초기화를 별도 대입으로 바꾸면 호출마다 값은 갱신되지만, 겹치는 수명의 컨트롤러가 같은 정보를 공유하고 동시 호출에서는 데이터 경쟁이 발생함.
std::shared_ptr의 별칭 생성자를 이용하면 컨트롤러와 정보 객체의 수명을 함께 관리할 수 있음.- 근본적인 해결책은
BasicWidgetController가 참조 대신BasicWidgetInfo를 복사해 보유하도록 수정하는 것임.
함수 지역 static 변수의 초기화 방식
- 함수 안에 선언된
static변수는 실행이 해당 선언에 처음 도달할 때 초기화되며, 이후 다시 도달해도 초기화되지 않고 기존 값을 유지함. - 수명 문제를 해결하려고 변수를
static으로 바꾸려는 시도가 있었음.
위젯 컨트롤러와 수명 문제
- 외부 위젯 라이브러리의
MakeWidget은WidgetController객체를 받아 위젯의 동작을 여러 시점에 조회함으로써 사용자 지정 동작을 제공함. - 애플리케이션은 기본 동작을 제공하는
BasicWidgetController와 위젯 종류 및 플래그를 담는BasicWidgetInfo를 사용함. 컨트롤러는BasicWidgetInfo의 참조를 보유하며, 동적 동작은 재정의하지 않음. Gadget::CreateWidget은WidgetKind::Vanilla와WidgetFlags::Openable을 기본값으로 설정하고,GadgetFlags::ClosableWidget이 설정된 경우WidgetFlags::Closable을 더한 뒤 컨트롤러와 위젯을 생성함.WidgetOptions생성자는GadgetOptions에 대한 참조를 저장함. 위젯이 나중에 컨트롤러에 플래그를 요청할 때 참조 대상인BasicWidgetInfo는 이미CreateWidget반환 시점에 소멸한 상태이므로, 잘못된 값을 반환하거나 충돌할 수 있음. 이는 해제 후 사용(use-after-free) 버그임.
static으로 수명을 늘릴 때의 문제
BasicWidgetInfo를 함수 지역static으로 만들면 함수 반환 이후에도 객체가 유지되어 충돌을 막을 수 있음.- 하지만 함수 지역
static은 클래스 인스턴스마다 따로 존재하는 것이 아니라 모든 인스턴스가 공유하며 최초 실행에서만 초기화됨. 따라서 첫 호출에 전달된 플래그가 정보에 고정되고, 이후 호출의 플래그는 반영되지 않아 여러Gadget이 첫 위젯과 같은 옵션을 사용함. - 초기화를 선언 시점이 아닌 별도 대입으로 수행하면 호출마다 정보가 갱신되고 가장 최근 플래그가 반영됨.
- 이 방식에서도 여러
Gadget이 수명이 겹치는 위젯을 생성하면 두 컨트롤러가 같은 정보를 공유함. 두 번째 호출에서info를 갱신하면 첫 번째 컨트롤러가 참조하는 값도 바뀜. CreateWidget을 두 스레드가 동시에 호출하면info에 대한 쓰기에서 데이터 경쟁이 발생하며 결과를 예측할 수 없음.
정보 객체의 수명을 함께 관리하는 방법
- 근본 문제는
BasicWidgetController가 정보 객체의 수명을 늘리려 하지만 참조만으로는 이를 보장할 수 없다는 점임. - 정보를
Gadget의 멤버로 옮기면 객체 수명을 다른 방식으로 연장할 수 있지만,BasicWidgetController를 가리키는 마지막std::shared_ptr가 소멸하기 전에m_info가 소멸하지 않도록 보장해야 함. 마지막 공유 포인터의 소멸 시점을 정확히 알기 어려우며, 위젯 수명과 연동될 것이라는 추정에 의존할 수 있음. std::shared_ptr의 별칭 생성자를 사용하면BasicWidgetController를 가리키면서도BasicWidgetControllerWithInfo전체의 수명을 관리할 수 있음.BasicWidgetControllerWithInfo는BasicWidgetInfo m_info와BasicWidgetController m_controller를 함께 보유함.m_controller초기화가m_info에 의존하므로 멤버 선언 순서는m_info가 먼저여야 함.- 이 구조체를
std::make_shared로 생성한 뒤, 별칭 생성자에 해당 객체와m_controller의 주소를 전달하면 컨트롤러를 가리키는 포인터의 수명이 정보 객체와 컨트롤러를 함께 보유한 객체의 수명에 연결됨.
근본적인 수정
- 이 수명 문제는 애플리케이션 자체에서 만든 문제이므로, 참조를 받는
BasicWidgetController를 수정해BasicWidgetInfo를 복사해 보유하는 것이 바람직함. - 컨트롤러가 정보 객체의 복사본을 소유하면 원래의 지역 변수는 함수 반환 시 소멸해도 문제가 없으며, 처음의
CreateWidget방식으로 돌아갈 수 있음.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요