회사에 있는 ros2로 뭔가를 만들고 있었는데
애플리케이션이 종료될 때 문제가 생겼다.
기존에는 Debug + LeakSanitizer를 이용하지 않아서 발견하지 않은 문제였다.
문제가 되는 코드는 대충 다음과 같다.
std::unordered_map<ERROR_CODE_T, RMW_ERRORCODE_T> g_errorMappingTable
{ { ... }, { ... }, { ... }, }; ERROR_CODE_T GetErrorCode(RMW_ERRORCODE_T error)
{ return g_errorMappingTable[error]; } bool IsSupported(RMW_ERRORCODE_T error) { return g_errorMappingTable.count(error) > 0; }
{ { ... }, { ... }, { ... }, }; ERROR_CODE_T GetErrorCode(RMW_ERRORCODE_T error)
{ return g_errorMappingTable[error]; } bool IsSupported(RMW_ERRORCODE_T error) { return g_errorMappingTable.count(error) > 0; }
에러코드를 보니 unordered_map의 소멸자가 호출되었는데 unordered_map을 참조하는 함수들이 호출되어서 생기는 문제였다.
전역변수니까 항상 제대로 값이 있을 것이란 믿음이 있었는데, 그렇지 않았던 것같았다.
그래서 다음과 같이 변경해서 문제를 피했다.
std::shared_ptr<std::unordered_map<ERROR_CODE_T, RMW_ERRORCODE_T>> g_errorMappingTablePtr
{new std::unordered_map<ERROR_CODE_T, RMW_ERRORCODE_T>
{ { ... }, { ... }, { ... } }}; ERROR_CODE_T GetErrorCode(RMW_ERRORCODE_T error) { auto errorMappingTablePtr{g_errorMappingTable}; if(errorMappingTablePtr == nullptr) return 0; return errorMappingTablePtr->at(error); } bool IsSupported(RMW_ERRORCODE_T error) { auto errorMappingTablePtr{g_errorMappingTable}; if(errorMappingTablePtr == nullptr) return false; return errorMappingTablePtr->count(error) > 0; }
{new std::unordered_map<ERROR_CODE_T, RMW_ERRORCODE_T>
{ { ... }, { ... }, { ... } }}; ERROR_CODE_T GetErrorCode(RMW_ERRORCODE_T error) { auto errorMappingTablePtr{g_errorMappingTable}; if(errorMappingTablePtr == nullptr) return 0; return errorMappingTablePtr->at(error); } bool IsSupported(RMW_ERRORCODE_T error) { auto errorMappingTablePtr{g_errorMappingTable}; if(errorMappingTablePtr == nullptr) return false; return errorMappingTablePtr->count(error) > 0; }
static variable의 lifetime이 프로그램 시작때부터 종료까지 지속되는건 맞음. 아마 다른 문제가 아닐까 싶은데
https://stackoverflow.com/questions/335369/finding-c-static-initialization-order-problems#335746
참고해보셈
맞을 거임. 사니타이저에서 use after free문제가 나타났났고 스택이 저 변수의 소멸자까지 갔거든.
진짜 C++는 pitfall이 너무 많다..
GetErrorCode에서 인자 타입이랑 반환 타입이랑 바뀌어야 할 거 같은데. 그게 키 타입이잖아
몰라 대충 저런 코드였다라고 생각하고 친 거임 회사 코드를 그대로 쓸 리가 없잖아
어플리케이션이 종료될때, 소멸자가 순서대로 호출되는 와중에 g_errorMappingTable는 이미 해제되었고, 그 다음에 소멸자가 불리는 객체에서 g_errorMappingTable를 사용한 문제인가?
그런 것같음
전역 초기화 안해서 10년만에 처음 실행된 코드가 값을 바꿔버려서 그 뒤로 원래 정상처리되어야할 코드들이 죄다 pass되어버려서 난리난적 있었는데 이게 알고보면 간단한데 당시 문제 터질때는 마치 정상로직들이 오류나니까 다들 그쪽 코드 검사하고 데이터 검사하고. 해당 프로세스로 기동된 서비스에만 문제되니까 어떤건 정상처리되고 어떤건 비정상처리되고 왜 그런 오류가 나는지 다들 찾기 힘들어 했던 대 사태가 터진적 있었는데. 진짜 그뒤로 전역변수 될수 있는한 제한하고 내가 스크립트 짜서 초기화 루틴에 없으면 오류나도록 하고 진짜 별 짓을 다해ㅛ었네
이런 문제로 C++에서 constexpr 생성자 및 소멸자가 구현되지 않은 클래스의 객체를 전역변수로 사용하지 말라고들 함