21.7.1.1. `SubscriptionData` 템플릿의 객체 내장 특성

21.7.1.1. SubscriptionData<T> 템플릿의 객체 내장 특성

앞 단원에서 살펴본 uORB::Subscription 래퍼 단원방식은 분명 훌륭했지만, Run() 함수 안에서 매번 구조체(sensor_gyro_s) 껍데기를 지역 변수로 선언하고 update() 함수에 주소(&)를 넘겨주어야 하는 약간의 번거로움이 있었다.

이 작은 오버헤드마저 거슬려 했던 완벽주의자 개발자들을 위해, PX4 코어팀은 이 껍데기 변수 자체를 아예 구독자(Subscriber) 객체 내부로 흡수 합병시켜버린 궁극의 진화체, SubscriptionData<T> 템플릿을 만들어 냈다.

이 마법의 템플릿을 사용하면 우리의 구독 코드는 한층 더 압축된다.

1. SubscriptionData<T> 의 우아한 선언

헤더 파일에서 구독자 객체를 선언할 때, 클래스 이름을 조금만 바꿔주면 신세계가 열린다.

#include <uORB/Subscription.hpp>

class CustomApp {
private:
    // 일반 Subscription 대신 'SubscriptionData'를 쓴다!
    uORB::SubscriptionData<sensor_gyro_s> _gyro_sub{ORB_ID(sensor_gyro)};
};

이렇게 선언된 _gyro_sub 객체는 단순한 통신망 라인(File Descriptor) 역할만 하는 것이 아니다. 자신의 뱃속에 sensor_gyro_s 구조체의 데이터를 담을 수 있는 거대한 버퍼(Buffer)를 아예 클래스 멤버 변수처럼 통째로 내장(Embedded) 하고 있다.

2. 극강으로 단순화된 Run() 로직

이제 Run() 함수 안에서 데이터를 꺼내 쓰는 모습은 감탄이 나올 정도로 직관적으로 변한다.
따로 빈 껍데기 변수를 선언할 필요도, 주소 연산자(&)를 쓸 필요도 없다.

void CustomApp::Run() {
    // 1. 새 데이터가 도착했는지 묻고, 도착했다면 알아서 자신의 뱃속 버퍼에 덮어쓴다!
    if (_gyro_sub.updated()) {
        _gyro_sub.update(); 
    }

    // 2. 뱃속에 들어있는 진짜 데이터(구조체)의 참조(Reference)를 꺼내온다.
    const sensor_gyro_s &gyro = _gyro_sub.get();

    // 3. 편안하게 변수를 꺼내 쓴다.
    process_data(gyro.x, gyro.y, gyro.z);
}

코드의 우아함뿐만 아니라, _gyro_sub.get() 메서드가 반환하는 것은 데이터의 복사본(Copy)이 아니라 내장된 버퍼의 상수 참조값(const Reference)이다. 덕분에 스택(Stack) 영역에 불필요한 구조체 사본이 생겨나지 않아 메모리가 극한으로 절약된다.

하지만 진정한 마법은 코드의 간결함보다 **초기 메모리 할당(Memory Allocation)**의 비밀에 숨어있다. 이 내장된 거대한 버퍼 구조체는 언제, 어디서 메모리를 할당받는 것일까?
동적 할당(Dynamic Allocation, malloc)이라는 시한폭탄을 피하고 커널의 힙(Heap)과 스택(Stack) 사이에서 줄타기하는 SubscriptionData의 정적 메모리 배치 전략을 다음 장(21.7.1.1.1)에서 속 시원하게 분해해 보자.