30 pontos por GN⁺ 2025-12-09 | 8 comentários | Compartilhar no WhatsApp
  • Um vídeo explica por que softwares de caças e foguetes, em que um bug de uma única linha pode levar a consequências fatais a Mach 1, removem a maior parte dos recursos de C++ para deixar apenas código previsível
  • Organiza a história das guerras de linguagens militares e da explosão do volume de código, da máquina mecânica de bombardeio do F-4 ao microprocessador confidencial do F-14, passando por Jovial, CMS-2 e Ada, que levou à exigência de uma única linguagem segura e de padrões rígidos
  • Mostra com código real como o padrão JSF C++, criado quando a Lockheed convenceu a usar C++ no desenvolvimento do F-35 em vez de Ada, proíbe exceções, recursão e alocação dinâmica de memória, substituindo isso por códigos de retorno, laços iterativos e reserva prévia de memória
  • Junto da informação de que os primeiros computadores de missão do F-35 usavam arquitetura da família PowerPC, compara, conectando o X-Plane 12 a um MFD feito pelo próprio autor, como o código que viola as regras JSF e o código em conformidade se comportam de forma diferente em voo real
  • Conclui que o padrão JSF depois influenciou padrões de segurança como NASA F-Prime, MISRA e AutoSAR e que, hoje, o mais adequado é seguir C++ Core Guidelines e C++ moderno sobre esse legado

Apresentação do palestrante

  • Desenvolvedor com experiência escrevendo código C++ para sistemas aeroespaciais e fazendo demonstrações para a força aérea
    • A explicação é baseada em experiência real usando C++ em sistemas com exigências elevadas de segurança
    • Usa como ambiente de demonstração um MFD (display multifuncional) criado por ele mesmo, composto por X-Plane 12, Web API, interface em Python e backend em C++

Software de voo e ambientes onde falha não é permitida

  • Em aplicações comuns, um crash termina com um reinício, mas em caças e foguetes a Mach 1 uma única falha vira desastre
    • A frase “A Mach 1 não há tempo para esperar o garbage collector” enfatiza a exigência de tempo real
    • Em situações em que uma linha errada pode levar a consequências fatais, a própria escolha dos recursos da linguagem precisa funcionar como mecanismo de segurança
  • O acidente da explosão do Ariane 5 em 1996 é apresentado como caso representativo
    • Uma exceção ocorreu ao converter um valor de viés horizontal em ponto flutuante de 64 bits para um inteiro de 16 bits, e essa exceção não foi tratada
    • A linguagem gerou o erro conforme a especificação, mas o sistema não conseguiu tratar a exceção, levando à destruição imediata de um foguete de 500 milhões de dólares
  • Tomando esse caso como referência, a equipe de projeto do F-35 escolheu a abordagem de cortar os próprios recursos da linguagem para não repetir o mesmo erro

História do software militar e guerras de linguagens

  • Na época dos primeiros caças, como o F-4 Phantom, praticamente não havia software
    • O computador de bombardeio era mais próximo de um dispositivo mecânico de precisão feito de engrenagens e cames, e o “código” era a própria forma dos cames metálicos
  • A situação mudou no projeto sigiloso do caça de superioridade aérea naval VFX (F-14 Tomcat)
    • Mais tarde foi revelado que, antes do Intel 4004 conhecido nos livros como “o primeiro microprocessador”, a Garrett AiResearch havia projetado um microprocessador para o F-14
    • Para controlar de forma ideal a asa de geometria variável do F-14, era necessário um microprocessador de alto desempenho, no qual foram gravadas cerca de 2.500 linhas de microcódigo para executar cálculos polinomiais
  • Depois disso, cada força armada adotou uma linguagem diferente, iniciando uma proliferação caótica de linguagens
    • A força aérea usava Jovial (Jules Own Version of the International Algorithmic Language), da família ALGOL
    • A marinha, recusando-se a usar a linguagem da força aérea, adotou CMS-2 no F-18
    • Com linguagens e arquiteturas de hardware diferentes, reutilização e verificação de código se tornaram praticamente impossíveis
  • Ao mesmo tempo, o tamanho do software por aeronave cresceu de forma exponencial
    • São citados números como cerca de 125 mil linhas no F-16A, cerca de 1 milhão no B-1 e cerca de 9 milhões no F-35 moderno
    • Um levantamento do Departamento de Defesa mostrou o uso de mais de 450 linguagens de programação e relatou que quase nenhuma tinha um padrão adequado

A imposição de Ada e seus limites

  • Para resolver essa desordem, o Departamento de Defesa criou a linguagem de alto nível única Ada e impôs fortemente seu uso
    • Para não usar Ada em um novo projeto, era preciso provar por que aquilo era impossível em Ada; caso contrário, não havia como ganhar o contrato
  • Ada é apresentada como uma linguagem muito adequada para áreas em que segurança e confiabilidade são críticas
    • Destaca-se seu projeto voltado a garantir segurança de memória e de tipos em sistemas críticos como os aeroespaciais
  • Mas, nos anos 90, começou a aparecer um descompasso com a realidade
    • Ao mesmo tempo, internet, Windows 95 e jogos comerciais baseados em C++ se tornavam o mainstream
    • Estudantes e desenvolvedores naturalmente migravam para GCC gratuito e C++, em vez de compiladores Ada caros
    • Compiladores Ada custavam milhares de dólares, o que dificultava o acesso individual e reduzia o pool de profissionais especializados na linguagem

F-35 e o nascimento do padrão JSF C++

  • O F-35 (Joint Strike Fighter) foi projetado desde o início como uma aeronave com peso enorme de software
    • Cálculos complexos como sensor fusion passaram a ocupar um papel central na operação da aeronave
  • A Lockheed Martin propôs ao Departamento de Defesa permitir o uso de C++ para atender a essas exigências
    • Era, na prática, um pedido para “quebrar” a política anterior de imposição de Ada, e para convencer disso era preciso controlar os riscos de C++
  • Nesse processo, o criador de C++, Bjarne Stroustrup, também teria participado como conselheiro no desenho das regras JSF C++
    • Ele afirmou ter ajudado diretamente na elaboração das regras JSF e comentou que, por isso mesmo, “pode haver viés” ao falar desse padrão
  • A ideia central é semelhante ao conceito de “remove before flight”
    • Como uma etiqueta que deve ser removida antes do voo, os recursos perigosos de C++ são removidos no nível da linguagem para usar apenas um subconjunto previsível
    • Assim, seria possível garantir segurança no nível de Ada e ainda permitir que desenvolvedores usassem parte da expressividade de C++

Hardware real e a analogia com o GameCube

  • Os primeiros blocos do F-35 usavam um computador de missão baseado em processadores Motorola G4 PowerPC
    • Essa informação foi divulgada em fontes como uma matéria da Aviation Today de 2003
  • O GameCube também usava um processador da família PowerPC, então é uma analogia curiosa por ser hardware de geração semelhante no nível do conjunto de instruções
    • Para uma correspondência geracional mais exata, outro console seria mais próximo, mas a analogia com o GameCube basta para entender o princípio

Ambiente de demonstração JSF C++: X-Plane 12 + MFD

  • Com base no X-Plane 12 e usando o add-on do F-35B (AOA Simulations), foi montado um ambiente de simulador de voo
    • A estrutura assina dados de voo em tempo real pela nova Web API do X-Plane e os exibe no MFD
  • O frontend é feito em Python, e o backend é um plugin em C++ que demonstra em comparação códigos que seguem ou violam as regras JSF
    • Altitude, velocidade, vento, envelope de voo, dados de navegação e outras informações são exibidos a partir de cálculos feitos em C++
  • Durante o voo, é executado de propósito um código C++ não padronizado que lança exceções, mostrando o MFD travando e causando problemas no voo; depois, o processo de correção com as regras JSF é mostrado passo a passo

As três restrições centrais do JSF: exceções, recursão e memória dinâmica

  • As três restrições mais enfatizadas no padrão JSF C++ são exceções (Exception), recursão (Recursion) e alocação dinâmica de memória
    • Além disso, também é especificado um limite máximo para a Cyclomatic Complexity (complexidade ciclomática) das funções

1) Proibição de exceções – AV Rule 208

  • JSF AV Rule 208: “exceções não devem ser usadas (exceptions shall not be used)”
    • Palavras-chave relacionadas a exceções, como try, catch e throw, são totalmente proibidas
  • O motivo central é a não determinismo do fluxo de controle
    • Como no Ariane 5, quando uma exceção ocorre, se o tratamento faltar ou o timing falhar, o sistema inteiro pode ruir de forma imprevisível
  • Bjarne é favorável ao tratamento de exceções no C++ moderno, mas explica que, na época em que o JSF foi projetado, a maturidade das ferramentas e a garantia de tempo real não permitiam dar suporte a exceções

    “JSF++ é para aplicações hard real-time e safety-critical (controle de voo); se o cálculo demorar demais, pessoas podem morrer, e com exceções não é possível garantir tempo de resposta”

  • No JSF, em vez de exceções, usam-se códigos de retorno (return code)
    • No exemplo do cálculo de altitude de densidade, cada situação de erro define um código de retorno diferente e o chamador interpreta esse valor para tratar o caso
    • Mesmo que haja falha de cálculo ou timeout, o programa inteiro não morre, e o lado chamador pode representar com segurança um “estado de erro”

2) Proibição de recursão – AV Rule 119

  • A AV Rule 119 proíbe que uma função chame a si mesma direta ou indiretamente, ou seja, proíbe recursão
    • A cada chamada recursiva, um novo stack frame é empilhado, e como é difícil conhecer o limite máximo de profundidade, o risco de stack overflow aumenta
  • Como exemplo, é usado o coeficiente binomial (binomial coefficient) empregado no cálculo de aeroportos alternativos em um plano de voo
    • Na versão fora do padrão, usa-se uma implementação recursiva da forma C(n, k) = C(n-1, k-1) + C(n-1, k)
    • Isso faz a profundidade das chamadas variar com a entrada, dificultando prever o limite superior de memória
  • Na versão em conformidade com JSF, o mesmo cálculo é reescrito como uma implementação iterativa baseada em laços
    • O código fica mais longo e menos elegante, mas entrega o mesmo resultado sem chamar a si próprio
    • Assim, fica mais fácil inferir estaticamente a profundidade de chamadas e o limite de uso de memória

3) Proibição de alocação dinâmica de memória – AV Rule 206

  • AV Rule 206: depois da inicialização, não se faz alocação nem liberação de memória
    • Durante a execução, ficam proibidas alocações em heap com new, delete ou até new interno via smart pointers
  • Isso ocorre por dois problemas principais
    • Em alocação de heap existe não determinismo temporal: não dá para saber quanto tempo vai levar
    • Se houver fragmentação do heap, pode acontecer falha de alocação mesmo restando capacidade total, por não existir um bloco contínuo grande o suficiente
  • Como exemplo, é mostrado um código fora do padrão que cria um buffer de histórico de IAS (velocidade indicada) com std::unique_ptr + array dinâmico para calcular rajadas de vento
    • É uma forma que viola as regras JSF por alocar um novo array durante a execução
  • Na versão em conformidade com JSF, usa-se um array de tamanho fixo com tamanho máximo definido por constante
    • Define-se uma constante como MAX_IAS_HISTORY, reserva-se a memória uma única vez na inicialização e depois apenas se rotacionam os índices
    • Assim, nenhuma alocação adicional ocorre em execução, garantindo previsibilidade de tempo e memória

4) Limite de complexidade ciclomática – AV Rule 3

  • A AV Rule 3 determina que a Cyclomatic Complexity (complexidade ciclomática) de uma função não pode passar de 20
    • A própria declaração da função vale 1 ponto, e if, while, for, switch, AND/OR lógico etc. acrescentam 1 ponto cada
  • Usando a implementação iterativa do coeficiente binomial como exemplo, o vídeo mostra como cada if, operação lógica e laço for aumenta a pontuação de complexidade
    • Como complexidade alta dificulta testes, verificação e análise, o objetivo do padrão é mantê-la abaixo de um certo limite

Legado do JSF: NASA F-Prime, MISRA, AutoSAR

  • As ideias do padrão JSF C++ depois se espalharam para outras áreas safety-critical
    • O framework de software de voo da NASA, F-Prime, foi publicado em 2017 e compartilha regras como proibição de alocação dinâmica de memória, de exceções e de recursão
  • No setor automotivo, houve uma evolução parecida
    • Surgiram padrões como MISRA C++ e AutoSAR (Automotive Open System Architecture), que definem regras de segurança para software automotivo
    • É mencionado que o guia AutoSAR C++14 faz referência explícita ao JSF, mostrando que a influência do JSF chegou também ao software automotivo
  • Como os carros modernos são praticamente “computadores com rodas”, esses subconjuntos de linguagem e regras de codificação se tornam uma base essencial da segurança

Conclusão: se você usar C++ hoje, o que deve seguir?

  • O padrão JSF C++ é apresentado, no contexto da época, como um feito de engenharia que reduziu uma linguagem complexa a um subconjunto previsível para garantir segurança no nível de controle de voo de um caça
  • Ao mesmo tempo, Bjarne Stroustrup recomenda que desenvolvedores de hoje sigam C++ Core Guidelines e C++ moderno
    • Isso porque a linguagem C++ e seu toolchain evoluíram nas últimas décadas, e hoje há muito mais condições de usar com segurança recursos como exceções e smart pointers
  • Ainda assim, o JSF continua sendo um caso importante da mentalidade de controlar a linguagem não por adição, mas por remoção
    • A mensagem final é que, mais do que decidir o que incluir, definir o que vai para a lista de remove before flight é o núcleo do projeto de sistemas safety-critical

8 comentários

 
lostid 2025-12-10

Como é para fins militares, acho que o resultado de equilibrar especificações de hardware baixas e recursos avançados acabou sendo o C++.

 
regentag 2025-12-10
  1. Ada tem um compilador open source chamado GNAT, baseado em gcc. Hoje em dia também existe o gnat-llvm, baseado em LLVM. E também há IDEs open source.

Estudantes e desenvolvedores provavelmente não aprenderam porque é uma linguagem do setor de defesa e tem pouca demanda, não porque as ferramentas sejam caras.

  1. Acho que usar Ada faz mais sentido para o objetivo do que usar Cpp "controlado". Especialmente em contextos safety-critical, Cpp parece deixar margem demais para erros.
 
iepics 2025-12-10
  1. A situação na época do desenvolvimento pode ter sido diferente.
 
m00nny 2025-12-09

No geral, parece ser sobre técnicas para escrever código previsível.
Embora o C++ seja usado como exemplo, os mesmos problemas também aparecem — e até de forma mais séria — em outras linguagens, como no caso de GC, então é uma pena que isso possa ser entendido como se estivesse discutindo as limitações do C++.
Se a ideia é não usar alocação dinâmica nem tratamento de exceções em C++, fica a dúvida se não seria muito mais fácil e rápido usar C e escrever seguindo esses princípios.

 
ndrgrd 2025-12-09

Pois é, também não entendo por que usar C++ em vez de C.

 
tsboard 2025-12-09

O C++ moderno é realmente estável, mas, se não for indispensável que seja C++, acho que vale considerar outras linguagens mais seguras.

 
coremaker 2025-12-09

Vamos usar a sintaxe de RUST de forma ESTRITA!

 
GN⁺ 2025-12-09
Opiniões do Hacker News
  • O documento do padrão de codificação em C++ do F-35 está aqui
    Tem 142 páginas e permite ver que tipo de restrições existem no desenvolvimento de software aeronáutico real
    • Dei uma olhada em alguns capítulos e parecem regras bastante razoáveis
      Só fiquei curioso sobre as exceções às regras com “shall”. Em projetos desse porte, os casos de exceção mostram a efetividade real do padrão
    • Em sistemas de tempo real, existe a regra de proibir alocação dinâmica de memória durante a operação
      Em 2005 isso podia ser aceitável, mas fico pensando como isso se aplicaria hoje em ambientes como drones baseados em IA
    • Fico curioso se essas regras são impostas por ferramentas de análise estática ou se os desenvolvedores precisam conhecê-las e segui-las manualmente
    • Também me pergunto se essas regras continuam válidas para software embarcado ou para dispositivos com poucos recursos
      Em especial, a proibição de stdio.h parece estranha no começo, mas lendo melhor dá aquela sensação de “faz sentido mesmo”
    • Quando vi esse documento pela primeira vez, alguém o usou como exemplo para dizer que “C++ para Arduino Uno ainda é C++”
  • Isso me fez lembrar de construções como a = a;, que já vi em código MISRA de verdade
    É um truque para não remover parâmetros não utilizados, mas fico em dúvida se esse tipo de regra realmente garante boa qualidade de código
    • Pesquisas sobre MISRA mostram que algumas regras reduzem defeitos, enquanto outras acabam até aumentando defeitos
      As diretrizes JSF são um documento de 2005 e têm limitações da época
      Também é interessante que Bjarne Stroustrup tenha proposto recentemente o conceito de “perfis de C++”
      No fim das contas, esse tipo de guia de estilo pode ser também uma questão de preferência estética
    • Em C, a forma padrão de lidar com uma variável apenas referenciada é usar (void)a;
    • Já escrevi bastante código de segurança crítica e muitas vezes as regras de codificação acabam piorando a qualidade geral
      O que realmente garante segurança é a qualidade do projeto e a arquitetura fail-safe
    • Em Zig, isso é tratado explicitamente com _ = a;
      Há também uma issue relacionada
    • Esses padrões não substituem revisão de código
      Eles servem mais para fornecer um critério comum de referência durante a revisão
  • Em software de satélite, de forma semelhante, o uso da STL é proibido
    O ponto central é a garantia da missão (mission assurance)
    Se você usa pilha ou heap, os endereços de memória das variáveis mudam, e isso pode causar problemas se uma célula específica falhar
    Quando todas as variáveis têm endereços fixos, dá para remapear só a célula defeituosa via patch e continuar a missão
    • Mas em C++ é impossível eliminar completamente o uso de pilha
      Variáveis locais, parâmetros e endereços de retorno acabam dependendo da pilha
      Recursão também se torna impossível, e variáveis temporárias ficam limitadas
      Então essa afirmação não parece realista
    • Em vez dessa abordagem manual, métodos automatizados como memória ECC ou codificação Reed-Solomon parecem mais eficientes
    • Então as variáveis seriam globais? E como seria feita a detecção de erro em células de memória?
    • Na prática, usa-se pilha sim. O heap é restringido, mas pools de memória são usados
      Dá para fixar os intervalos de endereço da pilha e do heap, mas ainda assim é discutível se isso é uma justificativa convincente
    • Nesse caso, também fico curioso sobre como os parâmetros de entrada/saída das funções são tratados
  • Existe a regra de que “todo if e else if deve obrigatoriamente ter um else ou um comentário”
    Eu também costumo deixar logs do tipo “esse caso jamais deveria acontecer”
    Em sistemas grandes, esse tipo de registro de situações excepcionais é muito útil para depuração
    • A instrução match do Rust é ótima nesse aspecto
      Ela obriga o tratamento explícito de todos os casos e, se um valor novo for adicionado ao enum, o compilador avisa
    • Eu também adiciono macros como _STOP ou _CRASH para interromper a execução imediatamente durante o debug
      Isso força a correção do problema na hora
  • Há um texto de um líder de engenharia explicando por que escolheram C++ em vez de Ada
    Em resumo, o motivo era “faltavam desenvolvedores Ada e toolchains”
    Mas hoje existe uma abertura maior à diversidade de linguagens, então parece possível que Ada fosse mais bem aceita
    A adoção de SPARK pela NVidia também parece um sinal de renascimento de Ada
    • Não gosto da lógica de que “faltam desenvolvedores Ada”
      Se o DoD tivesse exigido Ada, universidades e empresas teriam seguido esse caminho
      No fim, é possível aprender uma linguagem, e se Ada tivesse sido usada a confiabilidade do F-35 talvez fosse maior
    • Ainda hoje existem sete empresas vendendo compiladores Ada
      AdaCore, GHS, PTC ApexAda, DDC-I, Irvine, OC Systems, RR Software etc.
      No passado, compiladores Ada eram uma opção paga, enquanto C/C++ vinham por padrão, então escolas e empresas acabavam escolhendo C++
      A AdaCore contribuiu muito para a padronização ISO e para a disseminação do open source
    • Eu também gostaria que Ada voltasse a crescer
      Antigamente ela foi criticada em excesso, mas hoje parece até bastante atraente
    • Mas quando usei Ada na prática, não gostei da sintaxe
      A estrutura de arquivos e as flags de build eram complicadas, e Ada nem apareceu no ranking de linguagens da RedMonk
      Algumas funcionalidades eram interessantes, mas não passava uma experiência moderna de linguagem como Rust
  • Eu tinha curiosidade se a área de aviônica segue MISRA C/C++
    • Padrões de codificação são só uma parte; o essencial é a documentação de processo auditável
      Estruturas de certificação como DO-178C são o mais importante
    • Depende da empresa. Algumas usam diretamente C gerado por autocódigo de Matlab/Simulink
      E isso talvez seja, na verdade, a forma correta de escrever código de alta confiabilidade
    • Também varia por região. Os EUA usam padrões MIL, a Europa usa ECSS, a aviação usa DO-178C, e MISRA é amplamente adotado
  • O canal da LaurieWired no YouTube é realmente excelente
    • Em especial, a série de tutoriais de assembly ARM é excelente
  • Também recomendo os outros vídeos da Laurie
    Eles cobrem temas como engenharia reversa, ofuscação e compiladores
  • Ao ver a regra “não desreferencie ponteiro nulo” (AV Rule 174, MISRA Rule 107), pensei que era até estranho precisar explicitar isso
  • Também fico curioso sobre qual compilador é usado para buildar o código real do F-35
    Queria saber se foi algo feito pela própria LM ou um produto comercial