19.9.2. uORB 모듈의 불필요한 헤더 의존성(Dependencies) 제거 및 최적화
초보 PX4 개발자들이 흔히 저지르는 가장 무거운 죄악 중 하나는 헤더 파일(.h 또는 .hpp) 상단에 잡다한 빌드 의존성(Dependencies)을 쓰레기장처럼 방치해 두는 것이다.
처음 코드를 짤 때는 이것저것 필요할 것 같아 #include <uORB/topics/sensor_accel.h>, #include <uORB/topics/vehicle_gps_position.h> 등 수많은 헤더들을 잔뜩 포함(include)시켜 놓는다.
하지만 비행 제어 모듈이 완성되고 나면, 정작 그 코드 내부에서는 sensor_accel 데이터의 발톱 때만큼도 사용하지 않는데 헤더만 쓸데없이 덩그러니 남아있는 경우가 허다하다. 이 단 한 줄의 #include 타격이 드론 펌웨어의 컴파일(Compile) 생태계에 미치는 눈덩이 효과(Snowball Effect)는 실로 가공할 만하다.
1. C++ 빌드 파이프라인의 눈덩이 패널티
C++ 컴파일러(GCC)는 모듈을 빌드할 때 헤더 파일을 만나면, 그 헤더 파일이 품고 있는 또 다른 헤더들까지 수십 단계를 거슬러 올라가며 수천 줄의 코드를 메모리에 전부 복사-붙여넣기(Copy-paste Expansion) 해버린다.
만약 당신이 아무 이유 없이 vehicle_local_position.h를 당신의 작은 .hpp 클래스에 인크루드 시켜 놓았다고 가정해 보자.
누군가가 vehicle_local_position.msg 파일(uORB 메시지 원본)에 변수 하나를 추가하거나 수정하는 순간, PX4 빌드 시스템의 닌자(Ninja) 의존성 추적기는 **“아! 저 멀리 있는 너의 커스텀 모듈도 이 헤더를 참조하고 있군!”**이라며 구조체가 변경되었으니 너의 C++ 파일도 전부 처음부터 다시 리컴파일(Re-compile)하라는 끔찍한 연쇄 명령을 내려버린다.
이 때문에 make px4_sitl을 칠 때마다 내 코드는 아무것도 안 바꿨는데도 500개의 파일이 무의미하게 다시 빌드되는 지옥불 컴파일 타임(Compile Time) 참사가 발생하는 것이다.
2. 다이어트의 정석: 전방 선언 (Forward Declaration)의 예술
이 거대한 눈덩이 패널티를 가장 날카롭게 끊어내는 C++ 아키텍처의 비기(Secret Art)가 바로 **‘전방 선언(Forward Declaration)’**이다.
만약 당신의 클래스 헤더 파일(.hpp) 내부 구조체에서, 특정 uORB 메시지를 구조체의 멤버 변수가 아니라 오직 포인터(*)나 참조자(&)의 형태로만 들고 있다면? C++ 컴파일러에게 그 메시지의 구체적인 내부 구조(크기와 멤버 변수들)를 굳이 알려줄 필요가 전혀 없다. 포인터는 메모리 상에서 항상 고정된 8바이트(64비트 OS 기준) 짜리 주소 껍데기일 뿐이기 때문이다.
[최악의 비만형 헤더 패턴]
// MyCustomModule.hpp
#pragma once
#include <px4_platform_common/module.h>
#include <uORB/Subscription.hpp>
// ❌ 쓸데없이 원본 헤더 전체를 통째로 쑤셔 넣어버림!
#include <uORB/topics/vehicle_gps_position.h>
class MyCustomModule : public ModuleBase<MyCustomModule> {
private:
// 포인터로만 선언할 건데도 위에서 무거운 헤더를 가져왔다.
uORB::Subscription* _gps_sub;
vehicle_gps_position_s* _gps_data_ptr;
};
[아름다운 다이어트 및 종속성 격리 패턴]
위의 비만 코드를 아래와 같이 우아하게 성형 수술한다.
// MyCustomModule.hpp
#pragma once
#include <px4_platform_common/module.h>
// uORB 기반 클래스 헤더만 가져온다.
#include <uORB/Subscription.hpp>
// ✅ 전방 선언 (Forward Declaration):
// "컴파일러야, 나중에 저쪽 어딘가에 vehicle_gps_position_s 라는 이름의 구조체가
// 진짜로 정의되어 있을 테니, 일단 8바이트짜리 주소(포인터) 담을 공간만 만들어 놔라!"
struct vehicle_gps_position_s;
class MyCustomModule : public ModuleBase<MyCustomModule> {
private:
uORB::Subscription* _gps_sub;
// 전방 선언のおかげ로 무거운 #include 없이 포인터 선언에 성공!
vehicle_gps_position_s* _gps_data_ptr;
};
이 압도적인 차이를 보라. .hpp 파일에서 거대한 #include <uORB/topics/vehicle_gps_position.h> 구문을 뽑아내고 그 자리에 단 한 줄의 struct vehicle_gps_position_s;를 박아넣음으로써, 우리는 이 클래스를 이웃한 다른 수천 개의 컴파일 의존성(Dependency Hell)으로부터 완벽하게 격리(Isolate)시켜 버리는 데 성공했다.
단, 포인터를 통과하여 진짜로 내부에 있는 lat, lon 변수에 접근해 숫자를 꺼내야 하는 시점, 즉 소스 파일( .cpp)의 구현부 최상단에서는 어차피 구조체의 실제 크기가 필요하므로 그때 진짜 #include를 해주면 된다. (cpp 파일은 헤더와 달리 남들에게 참조되지 않으므로 연쇄 리컴파일 버그를 일으키지 않는다.)
이처럼 불필요한 헤더 인크루드 구문을 찾아내 전부 휴지통에 넣거나 전방 선언으로 교체하는 행위는, C++ 프로젝트의 컴파일 속도를 10배 이상 끌어올리고 펌웨어의 바이너리 무게를 깃털처럼 가볍게 만드는 글로벌 베스트 프랙티스의 정수이다.