21.3.2.1. 옵션 플래그 파싱 및 유효성 검사 루틴 작성

21.3.2.1. 옵션 플래그 파싱 및 유효성 검사 루틴 작성

앞선 단원(21.3.2)에서 우리는 px4_getopt() 파서를 활용하는 거대한 while 루프 뼈대를 구경했다. 이 루프는 단순히 문자열을 쪼개는 것을 넘어, 사용자가 입력한 명령어 문법이 우리가 의도한 규칙(Syntax)에 완벽하게 일치하는지 심사하는 ‘합법성 검문소(Validation Checkpoint)’ 역할을 겸한다.

이 문법 심사 로직을 얼마나 엄격하게 짜느냐에 따라 런타임 중 모듈이 포인터 에러로 죽어버릴지, 아니면 안전하게 사용법(Usage)을 안내하고 친절하게 종료될지가 결정된다.

1. px4_getopt()의 3대 안전장치 설계법

파싱 루틴을 작성할 때 반드시 포함되어야 하는 유효성 검사 기법들은 다음과 같다.

1.1 포맷 스트링(Format String)을 통한 합법적 스위치 정의

px4_getopt 함수의 3번째 인자로 들어가는 "v:f" 같은 문자열은 이 파서의 영혼과도 같다.

  • 글자의 의미: 이 문자열에 적힌 알파벳(예: v, f)만이 우리가 시스템에서 허용하는 합법적인 옵션 스위치(-v, -f)다. 만약 사용자가 -x 라는 엉뚱한 옵션을 콘솔에 쳤다면, 파서는 가차 없이 ? (물음표) 기호를 반환하며 switch-case 문의 default 블록으로 코드 흐름을 넘겨버린다.
  • 콜론(:)의 의미 (인자 강제): 콜론은 단순한 장식이 아니다. v: 라고 적으면, 파서는 사용자에게 **“옵션 -v 뒤에는 반드시 공백을 띄우고 무슨 숫자나 문자를 파라미터로 적어야만 한다”**고 강제한다. 만약 사용자가 custom_app start -v 라고만 치고 엔터를 눌렀다면, 파서는 값이 없음을 감지하고 곧장 에러 처리를 해준다. 반면 f 뒤에는 콜론이 없으므로, -f는 단독으로 쓰이는 불리언 켜짐/꺼짐 플래그(Boolean Target Flag)로 간주된다.

1.2 예외 처리 분기점 (?default)

사용자가 오타를 내거나 잘못된 명령어를 입력했을 때, 펌웨어는 크래시(Crash)나 세그먼테이션 폴트(Segmentation Fault)를 일으키면 안 된다.

        // ... switch 블록 내부
        case '?':
        default:
            PX4_ERR("Unrecognized option");
            custom_app_usage("See help string above.");
            return 1;

따라서 switch-case 문에는 반드시 ? 케이스나 default 대피소를 만들어, 그 즉시 파싱을 중단하고 return 1 로 스레드를 죽이면서 NSH 콘솔에 Usage (사용 설명서 텍스트)를 붉은 글씨로 출력해 주어야 한다.

1.3 포인터 오버런(Over-run) 방지

while 루프가 옵션 플래그(-v, -f) 체를 다 끝나고 돌고 나면, 이제 진짜 목적어가 되는 메인 커맨드(start, stop, status 등) 텍스트를 파싱할 차례다. 이때 myoptind 라는 인덱스 변수가 활용되는데, 이 변수는 argc의 경계를 넘지 않았는지 반드시 사전에 검증되어야 한다.

    // 옵션 파싱이 끝났는데 남은 필수 커맨드 단어가 아예 없는 경우 
    if (myoptind >= argc) {
        custom_app_usage("Missing command");
        return 1;
    }

    const char *verb = argv[myoptind]; // 이제서야 안전하게 접근 가능

이 세 줄짜리 검증 코드를 빼먹는 것은, 절벽 끝에서 눈을 감고 앞으로 한 걸음 걷는 것과 똑같다. 사용자가 custom_app (추가 명령어 없음)이라고만 치고 엔터를 눌렀는데 이 방어 코드가 없다면, 프로그램은 존재하지 않는 메모리 번지(argv[myoptind])를 읽으려다 Null Pointer Exception이나 쓰레기 값 참조로 인해 처참하게 뻗어버릴 것이다.

이 철통같은 검문소들 중에서도 가장 정밀한 컨테이너(메모리) 조작이 일어나는 곳이 두 곳 있다. 바로 while 루프를 돌리는 조건문 자체의 포인터 제어와, 파라미터로 받은 문자(String)를 숫자(Integer/Float)로 캐스팅하는 작업이다. 이 두 가지 핵심 기법을 각각 다음 장인 21.3.2.1.1과 21.3.2.1.2 단원에서 떼어내어 현미경 관찰을 해보자.