21.3.2.1.2. `optarg` 전역 변수 메모리 안전성 확보 및 정수/실수형 변환(`strtol`, `strtof`) 에러 핸들링

21.3.2.1.2. optarg 전역 변수 메모리 안전성 확보 및 정수/실수형 변환(strtol, strtof) 에러 핸들링

우리는 지역 변수 포인터(myoptarg)를 활용해 px4_getopt 파서루프를 스레드 관점에서 완벽하게 안전하게 구성해 내었다.

        // 파싱 루프 내부
        case 'v':
            // myoptarg 변수 안에는 사용자가 타자 친 '5'라는 글자가 들어있다.
            my_custom_value = atoi(myoptarg); 
            break;

이제 파서는 -v 깃발 뒤에 따라온 문자열 텍스트("5")가 적힌 메모리 주소를 myoptarg 포인터에 걸어서 우리에게 건네주었다. 우리는 이 텍스트 덩어리를 잘 씹어서 컴퓨터가 인식할 수 있는 순수한 숫자(Integer나 Float) 변수로 캐스팅(Casting)해야 한다.

초보 개발자들은 여기서 십중팔구 C 언어의 국민 함수인 atoi()atof()를 들이밀곤 한다. 하지만 항공 우주 소프트웨어(Flight Stack)인 PX4의 엄격한 코드 컨벤션에서는, 의존성이 강한 메인 파라미터 파싱에 저런 구시대적이고 맹목적인 캐스팅 함수의 사용을 절대 승인해주지 않는다.

1. atoi() / atof()의 함정과 불친절함

atoi를 쓰면 안 될까? 사용자가 -v 5 대신 오타를 내서 -v 5abc라고 쳤거나, 아예 숫자가 아니라 -v hello라고 문자를 입력했다고 가정해 보자.

atoi 함수는 매우 멍청하고 무책임하다. 이 함수는:

  1. 앞쪽에 아라비아 숫자가 있으면 읽을 수 있는 데까지만 대충 읽고 뒤의 에러 문자열(abc)은 무시해 버린다. (Silent Error)
  2. 아예 숫자가 아닌 문자열(hello)이 들어오면 에러를 뿜는 대신 조용히 숫자 0을 반환해 버린다!

만약 저 -v 옵션이 기체의 초기 추력(Thrust) 퍼센티지나, 모터의 PWM 주파수를 결정하는 파라미터였다면 как(어떻게) 될까? 사용자는 hello라고 잘못 쳤을 뿐인데, 펌웨어는 아무런 경고도 없이 추력을 0으로 세팅하고 하늘로 날아오르려(혹은 추락하리라) 할 것이다.

2. 철통 방어 무기: strtol()strtof()의 2단 포인터 검증

PX4 코어 메인테이너들은 이러한 잠재적 재앙을 막기 위해, 문자열 파싱 시 반드시 C 표준 라이브러리의 좀 더 진보된 함수인 **strtol(String to Long)**과 **strtof(String to Float)**를 사용하도록 강제한다.

이 함수들의 가장 강력한 무기는 바로 **‘두 번째 포인터 인자(End Pointer)’**에 있다.

#include <cstdlib> // strtol, strtof가 포함된 헤더

// ... 파싱 루프 내부
        case 'v': {
            char *endptr = nullptr;
            // 10은 10진법을 의미함
            long val = strtol(myoptarg, &endptr, 10); 

            // 유효성 검사 1: 아무 숫자도 읽지 못했거나, 쓰레기 문자가 뒤에 섞여있는가?
            if (myoptarg == endptr || *endptr != '\0') {
                PX4_ERR("Invalid integer value for -v: %s", myoptarg);
                return 1; // 즉시 스레드 종료
            }

            // 유효성 검사 2: 자료형(int)의 표현 범위를 초과하는가? (옵션)
            if (val < 0 || val > 100) {
                 PX4_ERR("-v value out of range (0-100)");
                 return 1;
            }

            my_custom_value = (int)val;
            break;
        }

2.1 방어벽의 작동 원리 (End Pointer)

strtol 함수는 숫자를 읽어나가다가 숫자가 아닌 글자(a, b, ., 등)를 만나는 순간 파싱을 멈춘다. 그리고 멈춰버린 바로 그 불법 글자의 메모리 주소를 endptr이라는 포인터에 쾅 하고 찍어준다.

우리는 이 endptr의 상태만 검사하면 입력값의 무결성을 100% 장담할 수 있다.

  1. myoptarg == endptr: 애초에 첫 글자부터 문자가 들어와서 숫자를 단 한 글자도 못 읽었다는 뜻이다. 에러!
  2. *endptr != '\0': 숫자를 좀 읽다가 마지막에 이상한 문자(예: 5abc)가 걸려서 널 포인터(\0)까지 깔끔하게 도달하지 못했다는 뜻이다. 에러!

이처럼 strtolstrtof의 포인터 추적(Pointer Tracking) 기법을 사용하면, 사용자가 타이핑한 텍스트 쓰레기가 컨트롤러의 중요한 수학 공식 안으로 흘러 들어오는 것을 원천 차단할 수 있다.

이제 커스텀 모듈을 NSH 콘솔에 무사히 띄우고 옵션을 예쁘게 깎는 방법까지 모두 마스터했다. 다음 장(21.3.3)에서는 실제 드론 하드웨어에 이 코드를 올리기 전, 내 노트북 모니터 안에서 안전하게 날려볼 수 있는 시뮬레이션(SITL) 컴파일 방법에 대해 알아보도록 하겠다.