F-35에 사용된 C++ 표준 - 전투기는 왜 C++ 기능의 90%를 금지하는가
(youtube.com)- 마하 1의 속도에서 한 줄 버그가 치명적 결과로 이어지는 전투기·로켓 소프트웨어가 왜 C++ 기능의 대부분을 제거해 예측 가능한 코드만 남기는지 설명하는 영상
- F-4의 기계식 폭격 컴퓨터, 비공개 F-14 마이크로프로세서, Jovial·CMS-2·Ada로 이어지는 군용 언어 전쟁과 코드 폭증이 단일 안전 언어와 엄격한 표준을 요구하게 된 역사를 정리
- F-35 개발 과정에서 Lockheed가 Ada 대신 C++ 사용을 설득하며 만든 JSF C++ 표준이 예외, 재귀, 동적 메모리 할당을 금지하고, 반환 코드·반복 루프·사전 메모리 예약 등으로 대체하는 방식등을 실제 코드로 시연
- 초기 F-35 미션 컴퓨터가 PowerPC 계열 아키텍처를 사용했다는 점과 함께, X-Plane 12와 자체 제작 MFD를 연결해 JSF 규칙을 어기는 코드와 준수 코드가 실제로 비행 중 어떻게 다른지 비교함
- JSF 표준이 이후 NASA F-Prime·MISRA·AutoSAR 같은 안전성 표준으로 이어졌고, 지금은 그 유산 위에서 C++ Core Guidelines와 현대 C++ 를 사용하는 방향이 더 적합하다는 결론을 제시
발표자 소개
- 항공우주 시스템용 C++ 코드를 작성하며 공군 대상 시연을 하던 경험이 있는 개발자
- 실제로 안전성 요구가 높은 시스템에서 C++를 사용한 경험을 바탕으로 설명 진행
- X-Plane 12와 웹 API, Python UI, C++ 백엔드로 구성된 직접 제작 MFD(다기능 디스플레이) 를 데모 환경으로 사용함
비행 소프트웨어와 실패가 허용되지 않는 환경
- 일반 애플리케이션에서 크래시는 재시작으로 끝나지만, 마하 1 전투기·로켓에서는 한 번의 실패가 곧 재난이 되는 환경임
- “마하 1에서는 가비지 컬렉터를 기다릴 시간이 없다” 는 문장으로 실시간성 요구를 강조
- 한 줄의 잘못된 코드가 치명적 결과로 이어지는 상황에서 언어 기능 선택 자체가 안전 장치가 되어야 함
- 1996년 Ariane 5 폭발 사고를 대표 사례로 제시
- 수평 바이어스 64비트 부동소수점 값을 16비트 정수로 변환하는 과정에서 예외가 발생했고, 이 예외가 처리되지 못함
- 언어는 규격대로 오류를 발생시켰지만, 시스템이 이 예외를 처리하지 못해 5억 달러에 달하는 로켓이 즉시 파괴되는 결과로 이어짐
- 이 사례를 기준점으로 F-35 설계팀은 같은 실수를 반복하지 않기 위해 언어 기능 자체를 잘라내는 접근을 택함
군용 소프트웨어의 역사와 언어 전쟁
- 초기 전투기인 F-4 Phantom 시절에는 사실상 소프트웨어가 없었음
- 폭격 컴퓨터는 기어와 캠으로 구성된 정밀 기계장치에 가까웠고, “코드”는 금속 캠의 형태 자체였음
- 기밀 프로젝트였던 해군 공중우세기 VFX(F-14 Tomcat) 에서 상황이 바뀜
- 교과서에서 “최초의 마이크로프로세서”로 알려진 Intel 4004보다 앞서, Garrett AiResearch가 F-14용 마이크로프로세서를 설계했다는 사실이 뒤늦게 공개됨
- F-14의 가변익(swing wing)을 최적 제어하기 위해 고성능 마이크로프로세서가 필요했고, 여기에 약 2500줄 수준의 마이크로코드가 태워져 다항식 계산을 수행함
- 이후 각 군이 서로 다른 언어를 채택하며 언어 난립이 시작됨
- 공군은 ALGOL 계열인 Jovial(Jules Own Version of the International Algorithmic Language) 를 사용함
- 해군은 공군 언어를 쓰지 않겠다며 F-18에 CMS-2를 채택함
- 서로 다른 언어·하드웨어 아키텍처를 쓰면서 코드 재사용과 검증이 거의 불가능한 상태가 됨
- 동시에 항공기당 소프트웨어 규모가 지수적으로 증가함
- F-16A 약 12.5만 라인, B-1 약 100만 라인, 현대 F-35는 약 900만 라인 수준으로 증가했다고 수치 예시를 제시함
- 국방부 조사에서 450개가 넘는 프로그래밍 언어가 사용 중이고, 제대로 된 표준을 가진 언어는 거의 없었다는 보고가 나옴
Ada 강제와 그 한계
- 이런 난립을 해결하기 위해 국방부는 단일 고급 언어 Ada를 만들고 강력한 사용 의무를 부과함
- 새 프로젝트에서 Ada를 쓰지 않으려면 “왜 Ada로는 불가능한지”를 입증해야 했고, 그렇지 않으면 계약을 따낼 수 없는 구조였음
- Ada는 안전·신뢰성이 중요한 분야에는 매우 적합한 언어로 소개됨
- 항공우주 같은 안전 중요 시스템에서 메모리·타입 안정성을 보장하기 위한 설계가 강조됨
- 하지만 90년대에 들어 현실과 괴리가 드러나기 시작함
- 한편에서는 인터넷, Windows 95, C++ 기반 상용 게임들이 주류를 형성함
- 학생·개발자들은 비싼 Ada 컴파일러 대신, 무료 GCC와 C++ 에 자연스럽게 몰림
- Ada 컴파일러는 수천 달러에 달해 개인이 접하기 어려웠고, 결과적으로 Ada 전문 인력 풀도 줄어드는 흐름을 보임
F-35와 JSF C++ 표준의 탄생
-
F-35(공동타격전투기, Joint Strike Fighter) 는 처음부터 소프트웨어 비중이 매우 큰 기체로 설계됨
- 센서 융합(sensor fusion) 같은 복잡한 계산이 기체 운용 핵심으로 자리 잡음
- Lockheed Martin은 이런 요구를 위해 C++ 사용 허용을 국방부에 제안함
- 기존 Ada 강제 정책을 사실상 “깨달라”는 요청이었고, 이를 설득하려면 C++의 위험을 통제할 방법이 필요했음
- 이 과정에서 C++ 창시자 Bjarne Stroustrup도 조언자로 참여해 JSF C++ 규칙 설계에 관여했다고 언급함
- 직접적으로 JSF 규칙 작성에 손을 보탰다고 밝혔고, 그만큼 이 표준에 대해 “편향이 있을 수 있다”고 스스로 언급함
- 핵심 아이디어는 “remove before flight” 태그와 같은 발상임
- 비행 전 제거해야 하는 태그처럼, C++에서 위험한 기능들을 언어 차원에서 제거해 예측 가능한 서브셋만 사용하는 방식임
- 이로써 Ada 수준의 안전성을 확보하면서도, 개발자는 C++의 표현력을 일정 부분 활용할 수 있게 됨
실제 하드웨어와 GameCube 비유
- 초기 F-35 블록은 Motorola G4 PowerPC 프로세서 기반 미션 컴퓨터를 사용했음
- 이 정보는 2003년 Aviation Today 기사 등에서 공개된 내용
- GameCube 역시 PowerPC 계열 프로세서를 사용하기 때문에, 명령어 집합 수준에서 유사한 세대의 하드웨어라는 점에서 재미있음
- 보다 정확한 세대 일치를 위해서는 다른 콘솔이 더 가깝지만, 원리를 이해하는 데에는 GameCube 비유로 충분
JSF C++ 데모 환경: X-Plane 12 + MFD
- X-Plane 12를 기반으로 F-35B 애드온(AOA Simulations)을 사용해 비행 시뮬레이터 환경을 구성함
- X-Plane의 새로운 웹 API로 실시간 비행 데이터를 구독하고, 이를 MFD에 표시하는 구조
- 프런트엔드는 Python으로 구성되고, 백엔드는 C++ 플러그인으로 작성되어 JSF 규칙을 따르거나 위반하는 코드들을 비교 시연
- 고도, 속도, 바람, 비행 엔벨로프, 네비게이션 데이터 등 각종 정보를 C++ 계산 결과로 표시함
- 비행 중 의도적으로 예외를 던지는 비표준 C++ 코드를 실행해 MFD가 죽고, 비행에 문제가 생기는 상황을 보여준 뒤, JSF 규칙을 적용해 이를 해결하는 과정을 단계적으로 보여줌
JSF의 세 핵심 제약: 예외, 재귀, 동적 메모리
- JSF C++ 표준에서 강조하는 세 가지 핵심 제약은 예외(Exception), 재귀(Recursion), 동적 메모리 할당임
- 여기에 더해 함수의 Cyclomatic Complexity(분기 복잡도) 상한도 명시됨
1) 예외 금지 – AV Rule 208
- JSF AV Rule 208: “예외는 사용하지 않는다(exceptions shall not be used)”
- try, catch, throw 등 예외 관련 키워드를 전면 금지함
- 핵심 이유는 제어 흐름의 비결정성 때문임
- Ariane 5처럼 예외가 발생했을 때, 핸들링이 누락되거나 타이밍이 어긋나면 시스템 전체가 예측 불가능하게 무너질 수 있음
- Bjarne는 현대 C++ 예외 처리에 우호적이지만, JSF가 설계될 당시에는 도구 성숙도와 실시간성 보장 문제 때문에 예외를 지원할 수 없는 상황이었다고 설명함
“JSF++는 하드 실시간·안전 중요 애플리케이션(비행 제어)용이기 때문에, 계산이 너무 오래 걸리면 사람이 죽을 수 있고, 예외로는 응답 시간을 보장할 수 없다”
- JSF에서는 예외 대신 반환 코드(return code) 를 활용함
- 밀도고도(density altitude) 계산 예시에서, 오류 상황마다 서로 다른 반환 코드를 설정하고 호출자가 이를 해석해 처리하게 구성함
- 계산 실패나 시간 초과가 발생해도, 프로그램 전체가 죽지 않고 호출 측에서 “에러 상태”를 안전하게 표현할 수 있음
2) 재귀 금지 – AV Rule 119
- AV Rule 119는 함수가 직접·간접으로 자기 자신을 호출하는 것, 즉 재귀를 금지함
- 재귀 호출마다 새로운 스택 프레임이 쌓이고, 최대 깊이 상한을 알기 어려워 스택 오버플로 위험이 커지기 때문
- 비행 계획 중 대체 공항 계산에 쓰는 이항계수(binomial coefficient) 를 예시로 듬
- 비표준 버전에서는
C(n, k) = C(n-1, k-1) + C(n-1, k)형태의 재귀 구현을 사용함 - 이는 호출 깊이가 입력에 따라 달라져, 메모리 상한을 예측하기 어렵다는 문제가 있음
- 비표준 버전에서는
- JSF 준수 버전에서는 같은 계산을 반복문 기반의 반복적(iterative) 구현으로 변경함
- 코드는 길고 덜 우아하지만, 자기 자신을 호출하지 않기 때문에 재귀 없이 동일한 결과를 제공함
- 이렇게 하면 함수 호출 깊이와 메모리 사용 상한을 정적으로 추론하기 쉬워짐
3) 동적 메모리 할당 금지 – AV Rule 206
- AV Rule 206: 초기화 이후에는 메모리 할당·해제를 하지 않는다
- 실행 중
new,delete, 스마트 포인터를 통한 내부new등 힙 할당을 금지함
- 실행 중
- 이유는 두 가지 주요 문제 때문임
- 힙 할당에는 얼마가 걸릴지 모르는 시간 비결정성이 존재함
- 힙이 조각화(fragmentation)되면, 총 용량이 남아 있어도 큰 연속 블록을 찾지 못해 할당 실패가 발생할 수 있음
- 예시로, 돌풍(gust) 계산을 위해 IAS(지시대기속도) 이력 버퍼를
std::unique_ptr+ 동적 배열로 만드는 비표준 코드를 보여줌- 실행 중 새로운 배열을 할당해 JSF 규칙을 어기는 형태임
- JSF 준수 버전에서는 최대 크기를 상수로 정의한 고정 크기 배열을 사용함
-
MAX_IAS_HISTORY와 같이 상수를 정의하고, 초기화 시 한 번만 메모리를 잡은 뒤 인덱스만 회전시키는 방식으로 운용함 - 이렇게 하면 실행 중에는 어떤 추가 할당도 일어나지 않아 시간·메모리 측면에서 예측 가능성이 확보됨
-
4) Cyclomatic Complexity 상한 – AV Rule 3
- AV Rule 3은 함수의 Cyclomatic Complexity(분기 복잡도) 가 20을 넘지 않아야 한다고 규정함
- 함수 선언 자체 1점, if·while·for·switch·논리 AND/OR 등이 모두 복잡도에 1씩 더해짐
- 이항계수 반복 구현을 예시로, 각 if·논리 연산·for 루프가 복잡도 포인트를 어떻게 증가시키는지 보여줌
- 복잡도가 높아질수록 테스트·검증·분석이 어려워지기 때문에, 일정 상한 이하로 유지하는 것이 표준 목표임
JSF 유산: NASA F-Prime, MISRA, AutoSAR
- JSF C++ 표준의 아이디어는 이후 다른 안전 중요 분야로 퍼져 나감
- NASA의 비행 소프트웨어 프레임워크 F-Prime은 2017년에 공개되었고, 동적 메모리 할당 금지, 예외 금지, 재귀 금지 같은 규칙을 공유함
- 자동차 산업에서도 비슷한 흐름이 이어짐
- MISRA C++, AutoSAR(Automotive Open System Architecture) 같은 표준이 등장해 자동차 소프트웨어 안전 규칙을 제시함
- AutoSAR C++14 가이드는 JSF를 명시적으로 참고했다고 언급되며, JSF의 영향력이 자동차 소프트웨어까지 이어졌음을 보여줌
- 현대 자동차는 사실상 “바퀴 달린 컴퓨터” 에 가까운 만큼, 이런 언어 서브셋·코딩 규칙이 안전을 지탱하는 핵심 기반이 됨
결론: 오늘 C++를 쓴다면 무엇을 따라야 하는가
- JSF C++ 표준은 당시 기준으로 복잡한 언어를 예측 가능한 서브셋으로 축소해, 전투기 비행 제어 수준의 안전성을 확보한 엔지니어링 성과로 제시됨
- 동시에 Bjarne Stroustrup는 오늘날 개발자에게 C++ Core Guidelines와 현대 C++를 따를 것을 권장함
- C++ 언어와 툴체인이 지난 수십 년간 발전했고, 예외·스마트 포인터 같은 기능도 안전하게 쓸 수 있는 환경이 많이 마련되었기 때문임
- 그럼에도 JSF는 여전히 언어를 추가가 아니라 “제거”로 통제하는 사고방식의 중요한 사례로 남음
- 무엇을 넣을지보다, 무엇을 remove before flight 리스트에 올릴지가 안전 중요 시스템 설계의 핵심이라는 메시지로 마무리
Hacker News 의견
- F-35의 C++ 코딩 표준 문서가 여기에 있음
142페이지짜리로, 실제 항공기 소프트웨어 개발에서 어떤 제약이 있는지 볼 수 있음- 몇 장 훑어봤는데 꽤 합리적인 규칙들로 보임
다만 “shall” 규칙의 예외가 궁금함. 이런 대규모 프로젝트에서는 예외 케이스가 표준의 실효성을 보여줌 - 실시간 시스템에서는 운영 중 동적 메모리 할당 금지 규칙이 있음
2005년 당시에는 괜찮았겠지만, 요즘 AI 기반 드론 같은 환경에서는 어떻게 적용될지 궁금함 - 이런 규칙을 정적 분석 도구로 강제하는지, 아니면 개발자들이 직접 숙지해야 하는지 궁금함
- 임베디드나 저사양 기기용 소프트웨어에도 이런 규칙이 유효한지 고민됨
특히stdio.h금지 같은 부분은 낯설지만, 읽다 보면 “그래, 맞는 말이네” 싶음 - 이 문서를 처음 봤을 때, 누군가가 “Arduino Uno용 C++도 여전히 C++이다”는 예시로 들었음
- 몇 장 훑어봤는데 꽤 합리적인 규칙들로 보임
- 실제 MISRA 코드에서 본 적 있는
a = a;같은 구문이 떠오름
사용되지 않는 파라미터를 제거하지 않기 위한 꼼수인데, 이런 규칙이 좋은 코드 품질을 보장하는지는 의문임- MISRA 관련 연구를 보면 일부 규칙은 결함을 줄이지만, 일부는 오히려 결함을 늘림
JSF 가이드라인은 2005년 문서라 시대적 한계가 있음
Bjarne Stroustrup이 최근 “C++ 프로파일” 개념을 제시한 것도 흥미로움
결국 이런 스타일 가이드는 미적 취향의 문제일 수도 있음 - C에서는 변수를 참조만 할 때
(void)a;로 처리하는 게 표준적인 방식임 - 실제로 안전 필수 코드를 많이 작성해봤는데, 코딩 규칙이 전체 품질을 떨어뜨리는 경우도 많았음
진짜 안전을 보장하는 건 설계 품질과 페일세이프 구조임 - Zig에서는
_ = a;로 명시적으로 처리함
관련 이슈도 있음 - 이런 표준은 코드 리뷰를 대체하지 않음
오히려 리뷰 시 참고할 공통 기준을 제공하는 역할임
- MISRA 관련 연구를 보면 일부 규칙은 결함을 줄이지만, 일부는 오히려 결함을 늘림
- 위성 소프트웨어도 비슷하게 STL 사용이 금지되어 있음
핵심은 미션 보증(mission assurance) 임
스택이나 힙을 쓰면 변수의 메모리 주소가 바뀌는데, 특정 셀이 고장 나면 문제가 생김
모든 변수가 고정 주소를 가지면, 고장난 셀만 패치로 옮겨서 임무를 계속할 수 있음- 하지만 C++에서 스택을 완전히 안 쓰는 건 불가능함
지역 변수, 매개변수, 반환 주소 등은 결국 스택에 필요함
재귀도 불가능해지고, 임시 변수도 제한됨
그래서 이 주장은 현실적이지 않음 - 이런 수동적 접근보다는 ECC 메모리나 Reed-Solomon 코딩 같은 자동화된 방법이 더 효율적임
- 그렇다면 변수는 전역으로 두는 건가? 그리고 메모리 셀 오류 감지는 어떻게 하는지 궁금함
- 실제로는 스택을 사용함. 힙은 제한되지만 메모리 풀을 사용함
스택과 힙의 주소 범위를 고정할 수는 있지만, 이게 설득력 있는 이유인지는 의문임 - 그렇다면 함수의 in/out 파라미터는 어떻게 처리하는지도 궁금함
- 하지만 C++에서 스택을 완전히 안 쓰는 건 불가능함
- “모든 if, else if에는 반드시 else나 주석이 있어야 한다”는 규칙이 있음
나도 비슷하게 “이 경우는 절대 발생하지 않아야 함” 같은 로그를 남김
대규모 시스템에서는 이런 예외 상황 로깅이 디버깅에 매우 유용함- Rust의 match 문이 이런 점에서 좋음
모든 경우를 명시적으로 처리해야 해서, enum 값이 추가되면 컴파일러가 경고해줌 - 나는 여기에
_STOP이나_CRASH매크로를 추가해서 즉시 디버그 중단시키기도 함
문제를 바로 수정하도록 강제하는 효과가 있음
- Rust의 match 문이 이런 점에서 좋음
- Ada 대신 C++을 선택한 이유를 설명한 엔지니어링 리드의 글이 있음
요약하면 “Ada 개발자와 툴체인이 부족했다”는 이유였음
하지만 지금은 언어 다양성에 대한 개방성이 커져서 Ada가 더 받아들여질 수 있을 것 같음
NVidia가 SPARK를 채택한 것도 Ada의 부활 조짐으로 보임- “Ada 개발자가 부족하다”는 논리는 싫어함
DoD가 Ada를 강제했다면, 대학과 기업이 따라왔을 것임
결국 언어 학습은 가능하고, Ada를 썼다면 F-35의 신뢰성도 더 높았을 것임 - 지금도 Ada 컴파일러를 파는 업체가 7곳이나 있음
AdaCore, GHS, PTC ApexAda, DDC-I, Irvine, OC Systems, RR Software 등임
과거에는 Ada 컴파일러가 유료 옵션이었고, C/C++은 기본 제공이라 학교나 기업이 C++을 택했음
AdaCore가 ISO 표준화와 오픈소스 확산에 큰 기여를 했음 - 나도 Ada가 다시 부흥하길 바람
예전엔 과도하게 비판받았지만, 지금은 오히려 매력적으로 느껴짐 - 하지만 실제로 Ada를 써보니 문법이 마음에 들지 않았음
파일 구조나 빌드 플래그가 복잡했고, RedMonk 언어 순위에도 Ada가 없었음
일부 기능은 흥미로웠지만, Rust처럼 현대적인 언어 경험은 아니었음
- “Ada 개발자가 부족하다”는 논리는 싫어함
- 항공전자 분야가 MISRA C/C++을 따르는지 궁금했음
- 코딩 표준은 일부일 뿐이고, 핵심은 감사 가능한 프로세스 문서화임
DO-178C 같은 인증 체계가 중요함 - 회사마다 다름. 어떤 곳은 Matlab/Simulink 오토코드로 생성된 C를 그대로 씀
오히려 이게 고신뢰성 코드 작성의 올바른 방식일 수도 있음 - 지역별로도 다름. 미국은 MIL 표준, 유럽은 ECSS, 항공은 DO-178C, 그리고 MISRA가 널리 쓰임
- 코딩 표준은 일부일 뿐이고, 핵심은 감사 가능한 프로세스 문서화임
- LaurieWired의 유튜브 채널이 정말 훌륭함
- 특히 ARM 어셈블리 튜토리얼 시리즈가 뛰어남
- Laurie의 다른 영상들도 추천함
리버스 엔지니어링, 난독화, 컴파일러 등 다양한 주제를 다룸 - “널 포인터를 역참조하지 말라”는 규칙(AV Rule 174, MISRA Rule 107)을 보고
이런 것도 굳이 명시해야 하나 싶었음 - 실제 F-35 코드 빌드에 어떤 컴파일러를 쓰는지도 궁금함
LM이 직접 만든 건지, 상용 제품인지 알고 싶음