21.5.2.1.2. C API (`param_get`) 대비 C++ 래퍼 클래스가 제공하는 포인터 타입 캐스팅 오류 사전 방지 효과

21.5.2.1.2. C API (param_get) 대비 C++ 래퍼 클래스가 제공하는 포인터 타입 캐스팅 오류 사전 방지 효과

앞서 우리는 ParamIntParamFloat 클래스가 알아서 값을 업데이트해 주는 마법 래퍼(Wrapper)라는 것을 배웠다.
그렇다면 굳이 이 복잡한 C++ 매크로 템플릿 구조를 쓰지 않고, 그냥 날것의 C 언어 API인 param_get()을 직접 호출해서 쓰면 안 될까? 사실 많은 구형 레거시(Legacy) 코드들이 아직도 이 원시적인 방법을 사용하고 있다.

하지만 C 언어 스타일의 파라미터 획득은 비행 중 **메모리 오염(Memory Corruption)**이라는 끔찍한 재앙을 불러올 수 있는 폭탄을 안고 있다.

1. 원시 C API의 치명적 설계 결함: void* 의 함정

일반적으로 C API를 사용하여 파라미터 값을 읽어오는 코드는 아래와 같다.

// 원시 C API 호로 (위험!)
param_t my_param = param_find("CUSTOM_APP_MAX");
float my_speed;

// C API의 두 번째 인자는 철저하게 void* 이다.
param_get(my_param, &my_speed); 

param_get 함수의 시그니처를 들여다보면 int param_get(param_t param, void *val) 형태를 띠고 있다.
함수의 두 번째 인자가 아무 타입이나 가리킬 수 있는 void* 포인터라는 사실은, C 언어 특유의 자유로움이자 저주의 시작이다.

만약 개발자가 야근에 찌들어 실수로 이 파라미터가 원래 int32_t (정수형) 타입 암호라는 사실을 잊어버리고 파서를 작성했다고 가정해 보자.

// 치명적인 실수: int32_t(정수) 파라미터를 float(실수) 변수로 담으려 함
int32_t param_val = ... ; // 내부 글로벌 서버에는 정수로 저장되어 있음
float my_speed;           // 개발자는 실수 변수를 준비함

// 런타임에 아무 에러 없이 통과해 버린다!
param_get(my_param, &my_speed); 

C 컴파일러는 &my_speed라는 포인터를 void*로 묵시적 형변환(Casting)시켜버리기 때문에, 빌드 타임에는 어떠한 붉은 줄도 생기지 않는다.
결과적으로 어떻게 될까? param_get 함수 내부는 받은 메모리 주소(실수형 변수의 4바이트) 위에다가 무식하게 정수형의 4바이트 비트(Bit) 패턴을 덮어써 버린다.

결국 원래 15라는 정수여야 할 값은, IEEE 754 부동소수점 해석기로 읽혀 0.0000000000000000000000000000000000000000021f 같은 터무니없는 비트(NaN 또는 극소수)로 둔갑해 버린다. 이 값이 제어기에 들어가는 순간 모터는 미쳐 날뛰고 드론은 그 자리에서 뒤집힌다.

2. C++ 템플릿의 컴파일 타임(Compile-Time) 철벽 방어

반면, 우리가 사용하는 DEFINE_PARAMETERS 매크로와 ParamInt, ParamFloat C++ 템플릿의 결합은 이러한 인간의 캐스팅 실수를 컴파일 단계에서 원천 차단해 버린다.

// 템플릿을 통한 강력한 타입 강제 (Type Enforcement)
(ParamFloat<px4::params::CUSTOM_APP_MAX>) _param_custom_app_max,

위 선언에서 ParamFloat 템플릿 클래스의 배를 갈라보면, 그 내부는 오직 float 타입만을 취급하도록 철저하게 강제 형변환(Static Typing) 에러를 뿜어내도록 설계되어 있다.

만약 누군가가 실수로 px4::params::CUSTOM_APP_MODE (이것은 분명 정수형 파라미터 심볼이다)를 실수 템플릿인 ParamFloat에 집어넣으려고 시도하면 어떻게 될까?

// 컴파일 타임 에러 발생! 빌드 즉각 중단!
(ParamFloat<px4::params::CUSTOM_APP_MODE>) _param_custom_app_mode,

이번에는 C++ 컴파일러(GCC)가 파라미터 심볼 테이블(Enum)에 매칭된 본래의 타입 서명과, 여러분이 주입하려는 템플릿(ParamFloat)의 타입이 불일치(Type Mismatch)함을 정확히 캐치하여 수만 줄의 끔찍한 템플릿 컴파일 에러 메시지를 뱉어내며 빌드 자체를 멈춰버린다.

정리하자면, PX4 코어팀이 이 기괴하고 복잡한 C++ 매크로 템플릿 구조를 억지로 도입한 이유는 단 하나다.
“비행 중에 메모리 포인터 캐스팅 오류로 기체가 추락하는 것을 겪느니, 차라리 빌드 타임에 컴파일러에게 뺨을 맞는 것이 수백 배 낫다.”

이제 파라미터를 선언하고 메모리에 결속하는 가장 안전한 법을 배웠으니, 다음 장(21.5.3)에서는 이 값이 비행 도중에 바뀌었을 때 어떻게 가장 빠르고 비동기적으로 내 런루프에 그 변동 사실을 알려주는지(Runtime Dynamics) 토픽 구독 모델 디자인을 해부해 보자.