Misra-C — Padrão para software em C

Em 27/05/2010, em Linguagem C, por sergioprado
Neste post escrevo sobre o padrão MISRA-C, desen­volvido por um con­sór­cio de empre­sas do setor auto­mo­tivo, cujo obje­tivo é definir um con­junto de regras e nor­mas de desen­volvi­mento de soft­ware embar­cado em lin­guagem C.
Com­par­tilhe!
  • Twitter
  • Facebook
  • LinkedIn
  • del.icio.us
  • Digg
  • email
  • PDF
  • Print

MISRA-C é um padrão de desenvolvimento de software em linguagem C desenvolvida pela mesma que dá seu nome, a Motor Industry Software Reliability Association. Este padrão tem foco em sistemas embarcados automotivos, mas suas práticas podem ser extendidas também para outras areas, como equipamentos médicos e aeroespaciais.

Seu principal objetivo é promover um conjunto de melhores práticas para o desenvolvimento de software seguro e portável em linguagem C. Foi inicialmente publicado em 1998, e melhorado no decorrer dos anos. A segunda e mais atual edição saiu em 2004, e foi entitulada de "Guidelines for the use of the C language in critical systems", e desde então vem sendo utilizada por empresas de vários ramos que desenvolvem software embarcado.

Infelizmente o documento não está disponível de forma gratuita, e quem quiser conhecer a fundo a norma precisa fazer um investimento para adquiri-laMas bastam algumas pesquisas no Google para encontrar bastante informação relevante à respeito do assunto.

Padrões de desenvolvimento

Antes de falar do padrão, vamos comentar sobre as motivações para usá-lo.

A linguagem C é uma das linguagens mais poderosas e flexíveis que existem. E é por isso que ela é extremamente popular em sistemas embarcados. Se bem utilizada, tem ótima performance, pode gerar um código bem pequeno e facilita bastante o acesso ao hardware. Mas o problema esta exatamente em como começamos a frase anterior: "Se bem utilizada...".

A linguagem C nas mãos de programadores inexperientes pode ser um problema. Sua flexibilidade é uma faca de dois gumes, pois dá poderes extras ao desenvolvedor, que talvez ainda não esteja preparado para usá-los.

E é por isso que os padrões de software existem. Para nos ajudar a usar esta ferramenta de programação (no nosso caso a linguagem C) da forma mais correta possível, segundo a norma, através de determinadas regras.

O padrão MISRA-C:2004

Voltando ao padrão, o MISRA-C:2004 contém 141 regras divididas em 21 categorias. Destas 141 regras, 121 são mandatórias e 20 são recomendadas. E para o código ser "MISRA-C Compliance", precisa estar de acordo com todas as 141 regras, sem exceção.

Infelizmente não temos espaço aqui para apresentar e discutir cada uma das regras (e até seria bem cansativo fazer isso), então vamos ver algumas das mais importantes (e controversas) regras deste padrão.

Rule 1: All code shall conform to ISO 9899 standard C, with no extensions permitted.

Tradução: Todo código deve estar de acordo com o padrão ISO 9899, sem permissão para o uso de extensões.

A primeira regra já é uma das mais polêmicas do padrão MISRA-C. O padrão ISO, também conhecido como C99, não foi desenvolvido com foco em sistemas embarcados, então como vamos ter um código para um sistema embarcado automotivo em conformidade com o C99? Um software embarcado normalmente requer algumas extensões para poder lidar diretamente com o hardware, como por exemplo a palavra chave interrupt usada em alguns compiladores para definir uma rotina de tratamento de interrupção. E isso pode ser um problema para o padrão MISRA-C.

Neste caso, o padrão MISRA-C permite alguns desvios à regra para extensões à linguagem. Estas extensões devem estar bem documentadas e centralizadas, como por exemplo, todos os acessos ao hardware em um único arquivo.

Rule 2.2: Source code shall only use /* ... */ style comments.

Tradução: O código-fonte deve usar apenas comentários no estilo /* ... */ 

Essa regra é mais para garantir um padrão de estilo de codificação. Nada de usar os comentários de linha "//" herdados do C++.

Rule 6.3: Typedefs that indicate size and signedness should be used in place of the basic types.

Tradução: Typedefs que indicam o tamanho e o sinal da variável devem ser usados no lugar dos tipos básicos.

Esse padrão deveria ser seguido por todos que pretendem garantir a portabilidade do código entre diferentes arquiteturas de hardware. Por exemplo, o código abaixo tem o objetivo de setar o bit 17 da variavel reg, considerando-se que int é um inteiro é de 4 bytes.

1
2
3
int reg;
 
reg |= 0x10000;

Quando portamos este código para uma arquitetura de 16 bits, este código não vai funcionar, já que um inteiro terá apenas 2 bytes. Então o que o padrão sugere é o código abaixo:

1
2
3
4
5
typedef int int32;
 
int32 reg;
 
reg |= 0x10000;

Neste caso, quando portamos o código, basta mudar a declaração do typedef para o tipo correto de um inteiro de 32 bits, por exemplo:

1
typedef long int32;

Rule 8.1: Functions shall have prototype declarations and the prototype shall be visible at both the function definition and call.

Tradução: Funções devem ter seus protótipos declarados e visíveis à sua definição e às suas chamadas.

Se a função é usada apenas no arquivo em que é declarada, o ideal é declarar seu protótipo no inicio do arquivo. Se a função é usada em outros arquivos/módulos, o ideal é declarar seu protótipo em um arquivo de header e incluí-lo em todos os modulos onde a função é declarada e usada. 

Rule 9.1: All automatic variables shall have been assigned a value before being used

Tradução: Todas as variáveis automáticas (locais) devem ter um valor atribuido antes de serem usadas.

Esta regra pode evitar muitos erros. Declarou uma variável local, inicialize-a. Declarou um buffer, use memset ou bzero para inicializá-lo. 

Rule 14.1: There shall be no unreachable code.

Tradução: Não deve existir código que nunca é executado.

A maioria dos compiladores hoje, quando configurado para otimizar por tamanho, acaba removendo do binário códigos que nunca são executados. Mas até para melhorar a leitura, é sempre bom fazer uma limpeza em códigos que nunca são executados, como o else do código abaixo:

1
2
3
4
5
6
7
void initBuffer(char *buffer, unsigned int size)
{
    if (size >= 0)
        bzero(buffer, size);
    else
        logError("Tamanho invalido!");
}

A função logError() nunca será executada já que size é uma variável sem sinal, portanto nunca será menor que zero.

Rule 16.2: Functions shall not call themselves, either directly or indirectly

Tradução: Funções não podem chamar elas mesmas direta ou indiretamente.

Você quer ver as consequências de um stack overflow? Use funções recursivas! Recursão pode ser às vezes uma solução elegante, mas consome muitos recursos de memória e processamento. Evite e busque soluções alternativas.

Rule 16.10: If a function returns error information, then that error information shall be tested.

Tradução: Se uma função retorna informações de erro, este erro deve ser checado.

Esta regra deve ser sempre seguida. Já vi muito código por aí chamando malloc() mas não verificando o resultado. Depois perdem horas para debugar o código.

Rule 20.4: Dynamic heap memory allocation shall not be used

Tradução: Alocação dinâmica de memória não deve ser usada.

OK, na minha opinião esta é a regra mais polêmica do padrão. Alocação dinâmica de memória pode ser a causa da maioria (e dos piores) bugs da sua aplicação. Fragmentação de memória e memory leak são os principais culpados. Mas às vezes precisamos do malloc() para resolver alguns problemas. Minha sugestão: use com muito cuidado e apenas como último recurso.

Rule 49: Tests of a value against zero should be made explicit, unless the operand is effectively Boolean.

Tradução: Verificar se uma variável está zerada deve ser feito de forma explicita, a não ser que o operando seja booleano.

Bom, logo de cara vou dizer que entendo os motivos, mas discordo desta regra. O que vocês acham mais legível, o primeiro if (padrão MISRA-C) ou o segundo?

1
2
3
4
5
6
7
if (bufferVazio == 0) { /* padrao MISRA-C */
    return;
}
 
if (!bufferVazio) {
    return;
}

Rule 104: Non-constant pointers to functions shall not be used.

Tradução: Ponteiros não constantes para funções não devem ser usados.

Discordo novamente! Em alguns casos, ponteiros para funções podem ser uma solução bem elegante, prática e sem muitos custos para o sistema. Por exemplo, dá para se implementar uma rotina de tratamento de uma máquina de estados com 3 ou 4 linhas de código usando ponteiro para funções (o meu artigo sobre máquina de estados fala sobre isso). 

Para finalizar, existem muitas ferramentas de análise estática de código que podem ser usadas para verificar se um código é compatível com o padrão MISRA-C como PC-LINT, CodeCheck e QA-C.

Ter um código compatível com o padrão não significa apenas que você vai desenvolver um código mais seguro e portável. Significa também que você vai estar mudando sua forma de programar, nem sempre para melhor. Interprete, critique e veja o que é melhor para a solução que esta buscando. Você não precisa seguir à risca todas as regras, apenas aquelas que se aplicam à seu projeto. E pode não estar 100% compativel com o padrão, mas vai com certeza ter melhorado a qualidade do seu código. De qualquer forma, vale a pena pelo menos dar uma olhada. O único risco que você vai correr é aprender um pouco mais com isso.

Um abraço,

Sergio Prado

VN:F [1.9.0_1079]
Rating: 9.3/10 (3 votes cast)
Misra-C - Padrão para software em C, 9.3 out of 10 based on 3 ratings
Compartilhe!
  • Twitter
  • Facebook
  • LinkedIn
  • del.icio.us
  • Digg
  • email
  • PDF
  • Print

Posts relacionados:

  1. Como se tornar um desenvolvedor de software embarcado
  2. Ferramentas open-source para pic
  3. Seminário Programação C/C++ para Sistemas Embarcados 2009

19 Respostas para “Misra-C — Padrão para software em C”

  1. Fernando Barboza Fernando Barboza disse:

    Oi Ser­gio!
        Parabéns pelos arti­gos e por dedicar parte do seu tempo em escreve-los.
        Uma pequena cor­reção na com­para­ção de códi­gos que fez, talvez até para deixar mais polêmica a com­para­ção entre os estilos.

    if (buffer­Vazio == 0) { /* padrao MISRA-C */
    return;
    }
     
    if (!buffer­Vazio) {
    return;
    }
        Para tornar os códi­gos equiv­a­lentes, fal­tou o detalhe do !.
     
    p.s. Tenho o cos­tume de usar o segundo estilo.

  2. sergioprado sergioprado disse:

    Olá Fer­nando!

    Você tem razão. Post cor­rigido. Obri­gado pelo toque.

    E con­tinue acom­pan­hando o blog!

    Um abraço!

  3. Alberto Fabiano Alberto Fabiano disse:

    Olá Sér­gio!
    Legal o posto, este é um dos meus assun­tos recor­rentes.
    Vale lem­brar que hoje há tam­bém o MISRA-C++ que tam­bém tem o mesmo foco, porém para a lin­guagem do tio Strous­trup! 
    [ ]s++;
    ./alberto –fabiano

  4. […] This post was men­tioned on Twit­ter by AlbertOFF Fabi­anON, Ser­gio Prado. Ser­gio Prado said: #blogsprado: Padrão MISRA-C http://goo.gl/V4wz […]

  5. Alexandre Rodrigues Alexandre Rodrigues disse:

    Quanto à Rule 49, con­cordo que !buffer­Vazio é mais legível e coer­ente, con­siderando que esta var­iável é booleana ou está sendo usada como tal.  Afi­nal “VERDADEIRO” ou “NOT VERDADEIRO” é mais que intu­itivo para qual­quer desen­volve­dor.
    Quando se trata de uma var­iável que con­tenha um número, como tam­Buffer, vejo muitos prob­le­mas quando usada desta forma. Neste caso pre­firo o padrão MISRA (tam­Buffer == 0).
    Um caso que vejo causar muita con­fusão é o uso do !str­cmp ou !mem­cmp. Estas funções foram cri­adas com três retornos exata­mente para ter­mos as três situ­ações pos­síveis str1 < str2, str1 = str2 e str1 > str2. Acho que não cabe usar­mos o NOT neste caso, prin­ci­pal­mente por !str­cmp sig­nificar exata­mente str1 = str2. É só pen­sar­mos nos resul­ta­dos que podemos exper­i­men­tar ao ter­mos um pro­gra­mador ini­ciante tra­bal­hando no nosso pro­jeto ten­tando enten­der o que o pro­gra­mador ante­rior quis dizer com esta com­para­ção. Afi­nal, “NOT str­cmp” sig­nifica que as strings são iguais?
    No mais, parabéns Ser­gio, grande amigo. Con­tinua com­pe­tente, inter­es­sado e dis­posto a ensi­nar o que sabe. Nossa área anda um tanto car­ente de profis­sion­ais assim.

  6. Poloni Poloni disse:

    Alexan­dre, a van­tagem de se voltar um valor igual a zero tem van­ta­gens na otimiza­ção, já que você tem instruções em Assem­bly que resolve as com­para­ções em ape­nas um ciclo.
    If (tamanho == 0) equiv­ale à if (!tamanho). Se o com­pi­lador for “esper­t­inho” ele notará que é a mesma coisa. Por­tanto, colo­car explici­ta­mente é inter­es­sante. Isso vale inclu­sive para booleano.
    if (bool_var == false) é mel­hor e mais rápido do que if (bool_var == true). A com­para­ção por zero nor­mal­mente é resolvida em um ciclo.

    Com relação a Rule 20.4 alguns sis­temas opera­cionais em tempo real resolve esse prob­lema de uma forma bem inter­es­sante. Quem tiver um tempo, dêem uma lida nesse artigo http://www.embedded.com/design/222300428;jsessionid=50P5LPREJXGYTQE1GHPSKHWATMY32JVN?pgno=1. Quem quiser ir direto ao assunto, vá a página 3.

  7. alex alex disse:

    Parabens pelo site.
    acho que você dev­e­ria apon­tar as fontes pois já havia lido exata­mente o que você pub­li­cou
    em out­ros sites espe­cial­iza­dos. abraço

  8. sergioprado sergioprado disse:

    Olá Alex,

    Você tem toda razão. As regras do padrão MISRA-C que usei neste post tirei dos links abaixo:

    http://www.embedded.com/columns/beginerscorner/9900659

    http://www.embedded.com/columns/technicalinsights/172301672

    Se você con­hecer mais alguma fonte inter­es­sante sobre o assunto deixe um comen­tário aqui.

    Um abraço!

  9. Rudolf Waller Rudolf Waller disse:

    Sér­gio, antes de mais nada, parabéns pelo post :)

    A regra 49 cita que “unless the operand is effec­tively Boolean”. Como a var­iável Buffer­Vazio dos seus exem­p­los é booleana, ambos exem­p­los estariam cor­re­tos segundo esta regra, não? Pelo que entendi, o que esta regra evita é algo como “if(!Contador) …“
     
    Ainda no mesmo assunto, na minha opinião, se o com­pi­lador gera um código difer­ente para (!xxx) e (xxx!=0), já pas­sou da hora de tro­car de com­pi­lador ;)
     
    Abraços,
    Rudolf

  10. Rudolf Waller Rudolf Waller disse:

    Oi Ser­gio,
     
    Dei uma pesquisada e vi que as regras 49 e 104 que você citou é do MISRA-C:1998. Não achei o equiv­a­lente destas regras na ver­são de 2004.
     
    Abraços,
    Rudolf

  11. sergioprado sergioprado disse:

    Olá Rudolf,

    Ótima obser­vação. A minha inter­pre­tação é que a regra quis dizer que você não pre­cisa fazer uma com­para­ção explicita ape­nas se sua var­iável for do tipo boolean. O exem­plo abaixo é per­mi­tido pelo padrão MISRA-C:

    bool buffer­Vazio;

    if (buffer­Vazio) {
    return;
    }

    Veja que a var­iável “buffer­Vazio” é do tipo bool. O tipo bool está especi­fi­cado no padrão ANSI C99.

    Um abraço,

    Ser­gio Prado

  12. Hugo Sobreira Hugo Sobreira disse:

    Caro Ser­gio,

    Parabens pela ini­cia­tiva, de fato sinto falta de arti­gos rela­ciona­dos ao tema na nossa lín­gua por­tuguesa.
    Venho tra­bal­hando com desen­volvi­mento de sis­temas críti­cos já há alguns anos e hoje uti­lizo a MISRA-C como padrão de cod­i­fi­cação. Vale à pena ressaltar a difer­ença entre padrão de cod­i­fi­cação e padrão de desen­volvi­mento (ou design). Para desen­volver sis­temas críti­cos é necess­sario ter estes padrões definidos. Inter­es­sante notar tam­bém que adop­tar uma padrão de cod­i­fi­cação não é ape­nas boa prática quando se trata de sis­temas críti­cos (SC): é mesmo uma obri­gação. Padrões de cod­i­fi­cação são requeri­dos por difer­entes nor­mas que defen­dem desen­volvi­mento de SC (veja a DO-178B, IEC-61508, EN-50128).
    Quanto ao padrão MISRA-C, os jus­ti­fica­ti­vas para cada regra são muito bem expli­cadas na própria norma, por isso recomendo sua leitura a qual­quer um que pense seri­amente em aplicá-la. Lá vc vai ver tam­bém que nem todas as regras são nor­ma­ti­vas, há tam­bém regras opcionais.

    O que tenho visto como exper­iên­cia geral é que MISRA-C não tem por propósito somente “edu­car” desen­volve­dores inex­pe­ri­entes. Desen­volver soft­ware para SC pode ser uma tarefa extrema­mente árdua pelo sim­ples fato de con­struções mais elab­o­radas da lin­guagem não serem permitidas.Tomando como exem­plo a regra comen­tada em seu artigo sobre alo­cação dinâmica de memória (regra 20.4, uma das requeri­das pela norma). Num sis­tema crítico, é impre­scindível saber a pri­ori quan­tos e quais são os recur­sos uti­liza­dos pela apli­cação. Mais que isso, é pre­ciso demon­strar, que a quan­ti­dade de memória uti­lizada nunca ultra­pas­sará o lim­ite disponível (suponha que o pro­grama falhe de uma maneira cat­a­stró­fica caso não haja memória disponível). Quando se usa alo­cação dinâmica de memória é extrema­mente difí­cil provar esta ‘pro­priedade’. Por­tanto MISRA-C fala muito tam­bém à comu­nidade de pro­gra­madores expe­ri­entes em C, mas que podem não estar dev­i­da­mente con­tex­tu­al­iza­dos as neces­si­dades da natureza deste tipo de aplicação.

    Abraços,
    Hugo Sobreira

  13. sergioprado sergioprado disse:

    Olá Hugo!

    Seu comen­tário é quase um novo post…:)

    Obri­gado pelas dicas valiosas e con­tinue apare­cendo por aqui.

    Um abraço!

  14. Sérgio MacGyver Sérgio MacGyver disse:

    Caro xará,   :-)
     
    Pre­tendia comen­tar a regra 49, sobre os testes de var­iável zer­ada, mas vi que out­ros já tece­ram seus comen­tários. No entanto, após você acres­cen­tar um comen­tário recon­hecendo que o if (!buffer­Vazio) é per­mi­tido pelo MISRA-C, não con­sigo enten­der em quê reside sua dis­cordân­cia do padrão.
     
    Quanto à regra 20.4, sobre o dynamic heap mem­ory allo­ca­tion, creio que o padrão con­dena não a alo­cação dinâmica em si, mas o método uti­lizado (heap mem­ory).
    Exis­tem out­ros esque­mas de alo­cação dinâmica em que a região alocável é pre­vi­a­mente par­ti­cionada em blo­cos de tamanho fixo, sendo alguns menores e out­ros maiores. Quando um código solicita memória, tenta-se entre­gar um bloco que pos­sua tamanho igual ou pouco maior que o solic­i­tado, caso disponível. Há perda de memória por entregar-se mais do que se pediu, mas não há o famiger­ado prob­lema de frag­men­tação de memória, em que há memória sufi­ciente, mas toda “espal­hada” pelo heap.
    Descon­fio que seja jus­ta­mente o prob­lema de frag­men­tação que esteja na causa das fal­has comu­mente asso­ci­adas à alo­cação de memória. Além da falta de teste de retorno do mal­loc, claro.
     
    Por fim, con­cordo com sua dis­cordân­cia (pun intended) da regra 104. Sou adepto da pro­gra­mação ori­en­tada a even­tos (+maquinas de esta­dos) em sis­temas embar­ca­dos, e essa é a mel­hor forma de se imple­men­tar even­tos (com funções de call­back).
     
    Descon­fio que o colega Hugo Sobreira é um con­hecido meu que tra­bal­hou na Motorola e talvez con­heça o esquema de alo­cação que citei acima. Se for você mesmo, bom te ver por aqui, Hugo! (eu tra­bal­hava no pro­jeto CESAR-Motorola).
     
    Sér­gio, por favor, encam­inhe esso meu comen­tário pro email do Hugo. Obrigado!

  15. sergioprado sergioprado disse:

    Olá xará! :)

    Com relação à regra 49, quando uma var­iável rep­re­senta um valor boolean, ou seja, que pode assumir ape­nas true (1) ou false (0), eu acho muito mais fácil de ler o código sem ter que com­parar com 0 ou 1, como exige o padrão. Com relação à regra 20.4, você fez uma ótima obser­vação. Ainda não tinha con­sid­er­ado esta hipótese. E o Hugo par­tic­ipa bas­tante do grupo sis_embarcados, do qual agora você tam­bém faz parte…:) Estou te enviando o email dele em PVT. Um abraço e con­tinue acom­pan­hando o blog!

  16. Parabéns Ser­gio,

    Ficou muito bom o seu artigo, padroniza­ção de código é um assunto muito impor­tante que merece ser dis­cu­tido. Toda ini­cia­tiva de estiliza­ção é bené­fica, levando a uma maior manuten­abil­i­dade do software.

    Como sem­pre seu blog é uma exce­lente fonte de infor­mações úteis e profissionais.

    Abraço.

  17. murilo murilo disse:

    Esse exem­plo da regra 49.
    bufferVazio não é um booleano, mesmo que declar­ado como um int?
    Quero dizer, acho que essa regra diz para que não façamos algo do tipo:
    if (!buffer_len)
    e que façamos
    if (buffer_len == 0) 
    o que é muito mais legível, o sig­nifi­caria “se o tamanho do buffer for igual a 0″ ao invés de “se não tamanho do buffer“
    Agora para o exem­plo que você disse, ape­sar de bufferVazio ser provavel­mente um int, ele assume val­ores booleanos então !bufferVazio faz mais sen­tido “se o buffer não estiver vazio”.
    Um abraço,
    Murilo

  18. sergioprado sergioprado disse:

    Olá Murilo, tudo bem?

    Seman­ti­ca­mente, “buffer­Vazio” é um booleano, mas sintati­ca­mente ele con­tinua sendo um int. O que o padrão quer nos dizer é que deve­mos fazer as com­para­ções explic­i­tas, ou seja, se dese­jamos ver­i­ficar se o buffer esta vazio, esta imple­men­tação está fora do padrão:

    if (buffer­Vazio) { … };

    E esta esta den­tro do padrão:

    if (buffer­Vazio == 1) { … };

    Eu con­cordo com você, e acho a primeira forma mais fácil de ler.

    Um abraço,

    Ser­gio Prado

  19. Sérgio MacGyver Sérgio MacGyver disse:

    Caros Sér­gio e Murilo,
    Pelo que entendi da regra 49, “Tests of a value against zero should be made explicit, unless the operand is effec tively Boolean”, no caso de bufferLen temos que fazer com­para­ções explíc­i­tas, pois é um con­ta­dor e não efe­ti­va­mente um booleano. Já no caso de buffer­Vazio, por se tratar efe­ti­va­mente de um booleano, ainda que implici­ta­mente um int, as com­para­ções if (buffer­Vazio) e if (!buffer­Vazio) são aceitas pelo MISRA-C.
    []s
    Sérgio

Deixe um comentário