Misra-C – Padrão para software em C

- por Sergio Prado

Categorias: Linguagem C Tags: , , , ,

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 adquirí-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

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.