프리스탠딩 구현이 무엇인지, 호스티드 구현과 어떻게 다른지, 그리고 현대 C++에서 프리스탠딩 표준 라이브러리가 어떻게 확장되고 있는지 살펴봅니다.
이전 글들 중 일부에서 우리는 freestanding 이라는 용어를 사용했습니다. 우리는 프리스탠딩 구현이 호스티드 운영체제의 지원 없이 동작하는 구현이라고 말했습니다. 힙 할당, 시스템 호출, 예외 지원을 일반적으로 사용할 수 없는 임베디드 시스템, OS 커널, 베어메탈 환경을 떠올려 보세요. C++ 표준은 이러한 제약된 환경에서도 동작해야 하는 언어와 라이브러리의 최소 부분집합을 정의합니다.
하지만 저는 이 용어를 조금 더 깊이 들어가 보고 싶었습니다. 일상적인 C++에서는 이 용어가 자주 등장하지 않기 때문입니다 — 적어도 표준을 더 주의 깊게 읽기 시작하거나, 그런 구현을 실제로 사용해야 하기 전까지는 말이죠.
이것은 꽤 논리적입니다. 어떤 것이 freestanding 이라고 불린다면, 우리가 익숙한 대안이 있어야 합니다. 실제로 그 이름이 있습니다: hosted.
지금 어떤 쪽에 있는지 확인하려면 __STDC_HOSTED__ 매크로를 살펴보세요. 값이 1이면 호스티드 구현이고, 0이면 프리스탠딩입니다.
몇 가지 핵심 차이점:
main 함수를 반드시 포함해야 합니다. 프리스탠딩 구현에서는 그 요구사항이 구현 정의입니다.
main()없이 프로그램은 무엇을 할까요? 정적 저장 수명을 가진 객체의 생성자와 소멸자를 포함하여, 시작과 종료 처리는 여전히 실행될 수 있습니다.
벤더가 프리스탠딩 구현을 아예 제공할 의무는 없다는 점도 참고하세요.
“구현 정의입니다” 를 넘어서 보겠습니다. 표준은 프리스탠딩 환경에서 사용할 수 있어야 하는 작지만 유용한 라이브러리 부분집합을 실제로 요구합니다.
일반적으로 저수준의 독립적인 유틸리티에 의존할 수 있습니다. <cstdint>, <cstddef>, <limits>, <type_traits> 같은 헤더는 그 최소 집합의 일부입니다. 이들은 고정 폭 정수 타입, 기본 크기 정의, 컴파일 시간 타입 검사, 수치 한계를 제공합니다 — 모두 운영체제에 의존하지 않는 것들입니다. <new> 의 일부도 사용할 수 있지만, 그렇다고 해서 동작하는 힙이나 전역 할당 기능이 반드시 있다는 뜻은 아닙니다.
눈에 띄게 빠져 있는 것은 본질적으로 OS 서비스에 의존하는 구성요소들입니다: <thread> 는 없고, <filesystem> 도 없으며, 일반적으로 <iostream> 도 없습니다. 동적 메모리와 예외 지원조차 보장되지 않습니다. 프리스탠딩은 언어와 라이브러리의 구성 요소를 제공하지만, 더 고수준의 편의 기능까지 제공하지는 않습니다.
프리스탠딩은 예전에는 매우 제한적이었습니다 — 표준 라이브러리가 거의 쓸 수 없다고 느껴질 정도로 말이죠. 이제는 더 이상 그렇지 않습니다.
C++20부터 시작해 C++23과 C++26까지 이어지며, 프리스탠딩 환경에서 사용할 수 있는 것을 확장하려는 분명한 노력이 있었습니다. 점점 더 많은 헤더가 프리스탠딩 친화적으로 바뀌고 있으며, 특히 OS 수준 서비스에 의존하지 않는 것들이 그렇습니다. 여기에는 알고리즘 라이브러리의 일부, 다양한 유틸리티 구성요소, 그리고 C++26에서는 std::span, std::expected, std::mdspan, out_ptr, inout_ptr 같은 스마트 포인터 어댑터가 포함됩니다.
그 결과, 예전에는 호스티드 구현에서만 사용할 수 있었던 알고리즘과 유틸리티를 운영체제 없이도 점점 더 활용할 수 있게 되었습니다. 모든 것을 처음부터 다시 만들어야 하지 않고도, 제약된 환경에서 더 표현력 있고 더 고수준의 코드를 작성할 수 있습니다.
이런 진화는 우연히 일어난 것이 아닙니다. 임베디드 시스템이나 게임 개발 같은 분야에서 강한 추진력이 있었고, 그곳의 개발자들은 성능과 더 나은 추상화를 모두 원합니다. 목표는 분명합니다: 현대 C++를 완전한 운영체제에서만이 아니라 어디에서나 사용할 수 있게 만드는 것입니다.
프리스탠딩은 사소한 기술적 세부사항이 아닙니다 — C++ 표준에서 근본적인 구분입니다. 이는 런타임 환경이 최소한의 보장만 제공할 때 실제로 무엇을 기대할 수 있는지를 명확히 해 줍니다: OS 없음, 힙 없음, 예외 없음. C++20부터 C++26까지 표준이 프리스탠딩 부분집합을 꾸준히 확장해 왔다는 사실은, 자원이 제한된 환경에서도 현대 C++를 실용적으로 만들겠다는 진지한 의지를 보여줍니다.
임베디드 소프트웨어, OS 커널, 베어메탈 타깃에서 작업한다면, 프리스탠딩이 무엇을 보장하고 무엇을 보장하지 않는지 이해하는 것은 직접적으로 중요합니다. 그리고 그렇지 않더라도, 이 구분을 아는 것은 특정 표준 라이브러리 구성요소들이 왜 그런 방식으로 설계되었는지, 특히 예외, 할당, OS 의존성과 관련해 이해를 더 날카롭게 해 줍니다.
여러분은 프리스탠딩 구현으로 작업하시나요? 어떤 환경을 대상으로 하고 계신가요?
이 글이 마음에 드셨다면,