Search

C++ 객체 수명과 암묵적 객체 생성

URL
생성 일시
2026/03/24 10:06
최종 편집 일시
2026/03/24 10:06
태그
D2
파일과 미디어
이전 글에서는 엄격한 앨리어싱 규칙의 관점에서 reinterpret_cast를 안전하게 사용하는 방법을 알아보았습니다. 이번 글에서는 한 걸음 더 나아가, 디스크나 네트워크에서 읽어 온 바이트 표현을 특정 타입으로 해석할 때 reinterpret_cast를 사용하는 패턴을 C++의 엄격한 표준에 비추어 살펴보겠습니다. 결론부터 말씀드리면, 이런 패턴의 상당수는 엄밀히 따지면 미정의 동작(UB, undefined behavior)에 해당합니다. 예를 들어 malloc()으로 할당받은 메모리를 구조체 포인터로 캐스팅해 곧바로 접근하는 방식은 C 언어에서는 합법적이지만, C++17까지는 미정의 동작으로 간주되었습니다. C와 C++의 이 차이는 C++의 객체 수명(object lifetime)이라는 개념에서 비롯됩니다. C에서 포인터는 단순한 메모리 주소이므로, 해당 주소의 바이트 표현을 원하는 타입으로 해석하기만 하면 됩니다. 반면 C++은 포인터가 가리키는 주소에 해당 타입의 살아 있는 객체가 존재해야 한다는 조건을 추가로 요구합니다. 이 규칙에 따르면 malloc()으로 할당받은 메모리에는 아직 살아 있는 객체가 없으므로, 여기에 접근하는 것은 미정의 동작이 됩니다. 이 규칙을 완벽히 이해하고 바이트 기반 접근을 모두 표준을 준수하는 코드로 수정하는 것은 매우 복잡하며, 때로는 사실상 불가능에 가깝습니다. 따라서 이 글의 목적이 기존의 실무 코드를 전면 수정하라고 권장하는 것은 아닙니다. 이미 이런 '관행적 코드'가 널리 쓰이고 있기 때문에 C++ 표준 역시 이를 합법화하는 방향으로 발전해 왔으며, 주요 컴파일러 구현체도 이에 맞춰 동작해 왔습니다. C++20에 도입된 암묵적 객체 생성(implicit object creation)이 대표적입니다. 이 글에서는 먼저 객체 수명의 기본 규칙을 살펴보고, 표준이 이런 실무적 관행을 어떻게 수용해 왔는지 따라가 보겠습니다. 이를 살펴보고 나면 실무에서 reinterpret_cast를 사용할 때 어느 선까지가 안전한지 깊게 이해할 수 있을 것입니다. 객체 수명의 기본 규칙 먼저 객체 수명의 기본 규칙을 정리해 보겠습니다. 여기서 미정의 동작으로 설명하는 패턴 중 일부는 표준이 발전함에 따라 합법적인 코드가 되었거나, 미정의 동작으로 간주되는 원인이 바뀌었습니다. 이 내용은 뒤이어 나오는 관행의 합법화에서 다루겠습니다. 하지만 먼저 기본 규칙을 알면 표준이 무엇을 합법화했는지 올바르게 이해할 수 있습니다. 저장 기간 저장 기간(storage duration)은 메모리가 할당되어 유지되는 기간을 의미합니다1. automatic: 지역 변수(스코프 기반) static: 전역 및 static 변수(프로그램 실행 전체) thread: thread_local 변수 dynamic: new와 delete로 관리되는 변수 한편 객체 수명은 해당 스토리지 위에서 특정 타입의 객체가 실제로 살아 있는 기간을 뜻합니다2. 시작: 적절한 정렬(alignment) 조건과 크기의 스토리지가 확보되고 초기화가 완료된 시점 종료: 소멸자 호출이 시작되거나, 스토리지가 해제되거나, 혹은 스토리지가 재사용되는 시점 일반적으로 저장 기간과 객체 수명은 일치합니다. 예를 들어 widget x;라고 선언하면 스토리지 확보와 동시에 widget 객체의 수명이 시작되며, 해당 스코프를 벗어나면 두 기간이 함께 종료됩니다. 그래서 평소에는 둘을 엄격히 구분할 필요성을 느끼지 못하지만, 사실 이 둘은 명백히 별개의 개념입니다. 하나의 스토리지에서 여러 객체 저장 기간과 객체 수명이 별개의 개념이라는 사실은, 동일한 스토리지 안에서 여러 객체가 순차적으로 생성되고 소멸할 수 있음을 의미합니다. 즉, 스토리지는 아무것도 없는 공간과 같아서, 그 위에 객체를 생성하고, 소멸시키고, 다시 새로운 객체를 생성하는 작업이 가능합니다. alignas(widget) unsigned char buf[sizeof(widget)]; // 저장 기간 시작 auto* w1 = new (buf) wid