Upload
dinhduong
View
213
Download
0
Embed Size (px)
Citation preview
UNIVERSIDADE DE BRASÍLIA
DEPARTAMENTO DE CIÊNCIA DA COMPUTAÇÃO
PROGRAMAÇÃO SISTEMÁTICA
Prof. Francisco A. C. Pinheiro
Padrão de Codificação e Estilopara a Linguagem C
(adotado para os trabalhos da disciplina)
VERSÃO 2008-2
Sumário
Doc-1. Use nomes que auxiliem a interpretação do elemento nomeado. 3Doc-2. Diferencie as classes dos elementos através da grafia dos identificadores 3Doc-3. Adote um estilo de formatação e documentação bem definido. 4Doc-4. Documente completamente a interface dos módulos. 5Doc-5. Documente a implementação dos módulos com o objetivo de facilitar a manutenção
do código.6
Mod-1. Use arquivos de cabeçalho e implementação para cada módulo. 6Mod-2. Evite inclusão múltipla dos arquivos de cabeçalho. 7Mod-3. Identifique cada módulo de forma única. 7Mod-4. Evite o uso de variáveis globais. 8Mod-5. Diminua a complexidade das interfaces. 8Mod-6. Use um único ponto de entrada e um único ponto de saída para módulos (e fun-
ções).8
TD-1. Utilize definição de tipos com indicação de tamanho e sinalização para os tiposbásicos.
9
TD-2. Use protótipos de função. 9TD-3. Use typedef para todas as estruturas (struct) e uniões (union), colocando a definição
de tipo antes da declaração.10
TD-4. Use o qualificador const para declarar as variáveis que não são modificadas, inclu-sive as paramétricas e os ponteiros.
10
TD-5. Limite as conversões de tipo. 11TD-6. Não use o mesmo identificador em múltiplas declarações. 12Mem-1. Minimize alocação dinâmica de memória. 12Mem-2. Use funções de memória que possibilitem a definição de limites de atuação. 13Mem-3. Inicie variáveis e ponteiros antes do uso. 14Ptr-1. Teste os ponteiros para valores válidos antes do uso. 14Ptr-2. Só use um ponteiro para acessar elementos de vetor se ele tiver sido declarado
como ponteiro para este vetor.14
Ptr-3. Só use aritmética de ponteiros com ponteiros que referenciam vetores ou elementosde vetores.
15
Ptr-4. 04. Só realize operações entre ponteiros, aritméticas ou relacionais (<, >, <=, >=),se eles apontarem para o mesmo vetor.
15
Ptr-5. Use índices em vez de ponteiros para acessar elementos de vetores. 16Ptr-6. Use ponteiros ao declarar grandes estruturas como argumentos em chamadas de
função.16
Ctr-1. Minimize a complexidade do fluxo de controle. 17Ctr-2. Um programa não deve conter elementos não utilizados, caminhos intransitáveis,
código morto ou não realizável.17
Ctr-3. O valor retornado por uma expressão que não tenha efeitos colaterais, ou por umafunção cujo valor de retorno seja void, deve ser utilizado.
17
Ctr-4. Não use funções com um número variável de argumentos. 18Ctr-5. Não use expressões como argumentos em chamadas a funções ou macros. 18
1
Err-1. Assegure que as expressões aritméticas produzirão os resultados com a acurácia eprecisão esperadas.
19
Err-2. Verifique o resultado de operações que podem resultar em erros ou exceções. 19Err-3. Verifique a ocorrência de erros de domínio e de imagem. 20Err-4. Use funções-envelope para realizar verificação não realizada pelas funções da bi-
blioteca padrão.20
Err-5. Teste a validade dos argumentos de entrada ao iniciar e a validade dos argumentosde saída ao finalizar a execução de cada função.
21
Err-6. Trate erros e exceções localmente. 21
2
1 Documentação e estilo
Recomendação Doc-1. Use nomes que auxiliem a interpretação do elemento nomeado.
RAZÃO: Favorece o entendimento, facilita a manutenção.COMENTÁRIO: O nome deve auxiliar a interpretação do elemento. Se o elemento for exportado, privilegia-se a inter-pretação; evita-se o uso de abreviações ou siglas que comprometam o entendimento de quem interpreta. Se o elementofor local, privilegia-se a escrita; pode-se usar abreviações e nomes curtos, já que o uso estará documentado.
• Nomes de variáveis devem ser expressões substantivadas.
• Variáveis e funções boolianas devem refletir o significado do valor verdade (true).
• Procedimentos devem ser nomeados pelo que eles fazem.
• Funções devem ser nomeadas pelo que elas retornam.
int valorTotal;int totalizaValores; // Desconformebool opcaoOK;bool opcaoInvalida; // Desconformevoid calcular_saldo() {...void saldo_conta() {... // Desconformefloat salario_semanal() {...float calcular_salario_semanal() {... // Desconformebool tamanho_valido() {...bool verificar_tamanho() {... // Desconforme
FONTE: [gnu, 5.4] [misra], [nureg, 2.4.1.2].
Recomendação Doc-2. Diferencie as classes dos elementos através da grafia dos identificadores
RAZÃO: Favorece o entendimento, facilita a manutenção.COMENTÁRIO: É desejável que as classes dos elementos possam ser diferenciadas graficamente sem, entretanto, pre-judicar a legibilidade (o que ocorre quando se usa afixos crípticos para indicar a classe). A grafia dos identificadoresdeve ser, antes de tudo, consistente: use um mesmo estilo sempre.
Variáveis, funções, parâmetros e membros de estruturas euniões.
Inicial minúscula.
Nomes de macros, valores constantes e constantes enume-radas.
Todos os caracteres em maiúsculas.
Tipos declarados (typedef). nomes terminados com o sufixo "_t",inicial maiúscula, outodos os caracteres em maiúsculas.
Tags de estruturas, uniões e enumerações. Como variáveis ou inicial maiúscula.Nomes usados como prefixo, por exemplo, rótulos identifi-cadores de módulos.
Todos os caracteres em maiúsculas.
Separe as palavras de um nome com sublinhado ou usandouma letra maiúscula inicial.
grafe_os_nomes_assim ougrafeOsNomesAssim.
EXEMPLO: O código abaixo ilustra algumas das grafias sugeridas:
3
extern ES_imprime_detalhe();#define TAXA (1.016)enum Escapes {BELL = ’\a’, RETRO = ’\b’, TAB = ’\t’, NL = ’\n’,
TABV = ’\v’, RET = ’\r’};enum Escapes valor_escape;const float PI = 3.1416;int valor_tx_mensal;typedef reg_a Registro_aluno;typedef reg_a registro_aluno_t;struct reg_a {int matricula;char *nome;
}Registro_aluno reg2_aluno;registro_aluno_t reg2_aluno;void imprime_media_alunos(int lista_matricula[]) ...
FONTE: [misra], [nureg].
Regra Doc-3. Adote um estilo de formatação e documentação bem definido.
RAZÃO: Favorece o entendimento, facilita a manutenção.COMENTÁRIO:
• Use chaves para definir blocos.
• Adote um único estilo para o uso de chaves: 1TBS ou 2TBS
• Os comentários deve ter a mesma indentação que o objeto sendo comentado.
• Indente comandos aninhados e estruturas de controle.
• Use um espaço entre operadores e operandos.
• Quebre expressões longas de forma que um operador inicie a próxima linha.
EXEMPLO: O estilo 2TBS é o estilo adotado pelo padrão GNU; é o estilo padrão da ferramenta indent. O estilo 1TBS(one true brace style) é também conhecido como estilo do kernel ou estilo K&R; pode ser obtido com o argumento-kr no programa indent.
4
#include <stdio.h>#include <math.h>
/*** \brief Exemplo formatacao 1TBS
*/int fun_1TBS(int arg){int res = 0, ind = 0, aux = 0;float tx_ano = 0, vr = 0;switch (arg) {case 1:case 5:
aux = aux + 2;/* segue fluxo */
case 7:/* Comentario no mesmo nivel
que o item comentado */res = res * aux;break;
default:res = 0;break;
}
if (res > 0) {/* espaço entre operadores */if (aux == -34 * tx_ano / 2) {
/* operador iniciandopróxima linha */
vr = (ind + sqrt(res + 2.06)/ tx_ano) / (aux+ ln(ind * aux));
} else {/* else em if’s aninhados */
}}
}
#include <stdio.h>#include <math.h>/*** \brief Exemplo formatacao 2TBS
*/intfun_2TBS (int arg){int res = 0, ind = 0, aux = 0;float tx_ano = 0, vr = 0;switch (arg){case 1:case 5:aux = aux + 2;/* segue fluxo */
case 7:/* Comentario no mesmo nivel
que o item comentado */res = res * aux;break;
default:res = 0;break;
}
if (res > 0){/* espaço entre operadores */if (aux == -34 * tx_ano / 2){/* operador iniciando
próxima linha */vr = (ind + sqrt (res + 2.06)
/ tx_ano) / (aux+ ln (ind * aux));
}else{/* else em if’s aninhados */
}}
}
FONTE: [gnu, 5.1], [misra], [nureg, 4.4.1.1].
Regra Doc-4. Documente completamente a interface dos módulos.
RAZÃO: Permitir o entendimento e o uso dos módulos sem a necessidade de acesso à sua implementação.COMENTÁRIO: A interface de um módulo é a única parte do módulo acessada por quem vai utilizá-lo. Deve, portanto,permitir o completo entendimento do seu funcionamento. Todos os elementos da interface de um módulo devem sercompletamente documentados, incluindo os elementos importados pelo módulo.EXEMPLO: O código abaixo ilustra a documentação de uma função usando as "tags"do doxygen:
5
/*** \brief Insere novo aluno
** \param mat Matrícula do novo aluno
* \param nome Nome do novo aluno
* \param tel Telefone do novo aluno
* \param ee Endereço eletrônico do novo aluno
** Insere o aluno, ajustando os apontadores de posição
* de modo a manter o arquivo ordenado pela matrícula do
* aluno. Se já existir um aluno com a mesma
* matrícula, a função retorna sem inserir.
*/voidARQ_A_inserir_aluno(int32_t mat, char_t nome[], char_t tel[], char_t ee[]);
FONTE: [misra], [nureg, 4.4.1.3].
Regra Doc-5. Documente a implementação dos módulos com o objetivo de facilitar a manutenção do código.
RAZÃO: A documentação da implementação é dirigida a quem irá manter o módulo. Pode omitir as descrições quesão óbvias a partir do código.COMENTÁRIO: As pessoas que irão manter o código devem ser capazes de entender a linguagem na qual ele estáescrito. Pode-se evitar documentação das partes óbvias:
• É importante documentar soluções de projeto que auxiliem o entendimento da implementação como um todo.
• É necessário documentar soluções não triviais.
FONTE: [misra], [nureg, 4.4.1.3].
2 Modularização
Regra Mod-1. Use arquivos de cabeçalho e implementação para cada módulo.
RAZÃO: Promove o encapsulamento, facilita a manutenção.COMENTÁRIO:
Arquivo cabeçalho Arquivo implementação
• Deve conter apenas os elementos exportadospelo módulo.
• Todos os elementos devem ser declarações detipos ou declarações extern.
• Deve conter todas as definições.
• Todos os elementos usados apenas no módulodevem ser declarados static.
EXEMPLO:
6
/* prog_mod1.h
* MOD1: ARQUIVO CABEÇALHO */
#ifndef MOD1#define MOD1#include "tipos.h"typedef struct tx_mensais taxa_t;struct tx_mensais{char_t cod;float64_t v1;
};extern taxa_t val_tx;extern const float32_t veloc;extern uint16_t salario_medio(void);
#endif
/* prog_mod1.c
* MOD1: ARQUIVO IMPLEMENTAÇÃO */
/* inclusão cabeçalhos do sistema */#include <stdio.h>/* inclusão cabeçalhos locais */#include "prog_mod1.h"
/* Definições globais */taxa_t val_tx;const float32_t veloc = 34E6;
/* Declarações e definições locais */typedef struct nodo_desc nodo_t;struct nodo_desc{int8_t matr;nodo_t *prox;
};
/* Declarações de funções locais *//* (protótipos) */static void calcular_salario(void);
/* Definições das funções */uint16_tsalario_medio() { ... }static voidcalcular_salario() { ... }
FONTE: [misra, 2-03-01, 2-03-02, 2-03-03], [nureg].
Regra Mod-2. Evite inclusão múltipla dos arquivos de cabeçalho.
RAZÃO: Facilita a manutenção e o desenvolvimento, evitando os erros decorrentes de múltiplas definições de elemen-tos contidos nos cabeçalhos.EXEMPLO:
/* inicio do arquivo */#ifndef idModulo#define idModulo/* conteúdo do arquivo */
#endif/* fim do arquivo */
/* inicio do arquivo */#if !defined(idModulo)#define idModulo/* conteúdo do arquivo */
#endif/* fim do arquivo */
FONTE: [misra, 2-03-01, 2-03-02, 2-16-02], [nureg].
Regra Mod-3. Identifique cada módulo de forma única.
7
RAZÃO: A unicidade do rótulo (e do nome) facilita a identificação do módulo no qual um elemento é definido.COMENTÁRIO: O nome de um módulo deve identificar sua função dentro do sistema. O rótulo de um módulo, contendoaproximadamente três letras maiúsculas, é usado para compor a identificação dos seus elementos exportados. Observeque nem todos os elementos exportados por um módulo precisam ter o rótulo do módulo em seu nome. O importanteé manter a clareza do significado do nome.EXEMPLO: No exemplo abaixo o nome ES é usado como rótulo.
/* prog_ent_sai.h
* ES: Módulo de entrada e saída */#ifndef $$MOD_ES#define $$MOD_ES#include "tipos.h"#define MAX_LIN (200)extern bool_t ES_estado_sumario;extern void ES_imp_totais(void);...
#endif
FONTE: [misra], [nureg].
Recomendação Mod-4. Evite o uso de variáveis globais.
RAZÃO: Variáveis globais aumentam o acoplamento e dificultam a manutenção.COMENTÁRIO: Quando necessário, deve-se manter as definições das variáveis globais em um único arquivo de imple-mentação, com suas declarações no arquivo cabeçalho correspondente.FONTE: [misra], [nureg].
Recomendação Mod-5. Diminua a complexidade das interfaces.
RAZÃO: Diminui o acoplamento e facilita a manutenção.COMENTÁRIO: Interfaces complexas dificultam o entendimento, além de contribuirem para aumentar o acoplamentoentre módulos. Em geral deve-se diminuir o acoplamento entre os módulos (e funções):
• Evitando o uso de variáveis globais.
• Diminuindo o número de parâmetros.
• Usando vetores ou estruturas.
• Evitando expressões nas listas de argumentos.
FONTE: [misra], [nureg, 4.1.1.3, 4.4.2.2], [iso, 6.7.5.3].
Recomendação Mod-6. Use um único ponto de entrada e um único ponto de saída para módulos (e funções).
RAZÃO: O uso de pontos únicos de entrada e saída facilita a análise e o teste do código, adere à definição usual demódulo, além de melhorar a compreensão do fluxo de controle e evitar valores de retorno indefinidos.COMENTÁRIO: Embora C não possua mecanismo para múltiplos pontos de entrada (explícitos), é possível chamarqualquer endereço de memória atribuindo-se um inteiro a um ponteiro para função.EXEMPLO: No código abaixo o valor de retorno de aux é indefinido quando a é diferente de zero.
8
#include <stdio.h>int aux(int);
intmain(int nargs, char *args[]){printf("%d\n", aux(0));printf("%d\n", aux(3));
}intaux(int a){if (a == 0)return 123;
}
FONTE: [misra, 2-06-06-05], [nureg, 4.1.2.4].
3 Tipos de dados
Regra TD-1. Utilize definição de tipos com indicação de tamanho e sinalização para os tipos básicos.
RAZÃO: Os tipos básicos em C/C++ têm seus tamanhos e implementações dependentes do compilador e da plataformaalvo da compilação. O padrão da linguagem estabelece apenas limites básicos.EXEMPLO: O uso de definição de tipos para os tipos básicos favorece a portabilidade. Os valores abaixo são baseadosna implementação do GNU GCC para o cabeçalho <stdint.h>.
#ifndef DECL_TIPOS#define DECL_TIPOS#include <stdbool.h>typedef bool bool_t;typedef char char_t;typedef unsigned char uint8_t;typedef unsigned short uint16_t;typedef unsigned int uint32_t;typedef unsigned long uint64_t;
typedef signed char int8_t;typedef signed short int16_t;typedef signed int int32_t;typedef signed long int64_t;typedef float float32_t;typedef double float64_t;typedef long double float128_t;#endif
FONTE: [misra, 2-03-09-02], [nureg, 4.1.2.6], [iso, 7.18].
Regra TD-2. Use protótipos de função.
RAZÃO: O uso de protótipos torna possível a verificação de tipos dos parâmetros.COMENTÁRIO: O tipo de uma função que não retorna valor e a lista de parâmetros de uma função sem parâmetrosdevem ser declarados void.FONTE: [misra], [nureg, 4.1.2.5], [iso, 6.5.2.2, 6.7.5.3].
9
Regra TD-3. Use typedef para todas as estruturas (struct) e uniões (union), colocando a definição de tipo antes dadeclaração.
RAZÃO: Favorece o entendimento, eliminando a necessidade das palavras extras struct e union na declaração detipos.EXEMPLO: O uso de typedef também permite a declaração circular de tipos.
typedef struct um um_t;typedef struct dois dois_t;
struct um{int num_a;dois_t *elem;
};
struct dois{int num_b;um_t *elem;
};
FONTE: [misra], [nureg].
Regra TD-4. Use o qualificador const para declarar as variáveis que não são modificadas, inclusive as paramétricase os ponteiros.
RAZÃO: Se uma variável não deve ser modificada, ela deve ser declarada de modo a assegurar este comportamento.EXEMPLO:
#include <stdio.h>
void funcao(int , int *, const int, int const, const int *, int * const);
intmain(int nargs, char *args[]){int p1 = 1, p2 = 2, p3 = 3, p4 = 4, p5 = 5, p6 = 6;printf("%d %d %d %d %d %d\n", p1, p2, p3, p4, p5, p6);funcao(p1, &p2, p3, p4, &p5, &p6);printf("%d %d %d %d %d %d\n", p1, p2, p3, p4, p5, p6);return 0;
}voidfuncao(int p1, /* p1 é cópia (não é constante) */
int *p2, /* p2 é ponteiro (não é constante) */const int p3, /* p3 é cópia (constante) */int const p4, /* p4 é cópia (constante) */const int *p5, /* p5 é ponteiro (conteúdo constante) */int *const p6) /* p6 é ponteiro (constante) */
{printf("funcao: %d %d %d %d %d %d\n", p1, *p2, p3, p4, *p5, *p6);p1 = 11;
*p2 = 22;/* p3 = 33; atribuição inválida *//* p4 = 44; atribuição inválida */p5 = p2;
10
/* (*p5) = p4; atribuição inválida *//* p6 = p2; atribuição inválida */(*p6) = p4;printf("funcao: %d %d %d %d %d %d\n", p1, *p2, p3, p4, *p5, *p6);
}
FONTE: [misra, 2-07-01, 2-08-04-03], [nureg].
Recomendação TD-5. Limite as conversões de tipo.
RAZÃO: As regras para conversões de tipo, incluindo as promoções inteiras, são complicadas e podem gerar resultadodiferente do pretendido.COMENTÁRIO: Limite as conversões explícitas de tipos e elimine as conversões implícitas ou automáticas. Eviteoperações cujos operandos possuem múltiplos tipos de dados. Também evite misturar em uma mesma expressãovariáveis sinalizadas e não sinalizadas.EXEMPLO: No programa à esquerda, para realizar a comparação o inteiro i é convertido para um valor não sinalizado,fazendo com que assuma o maior valor possível para este tipo. Uma conversão semelhante ocorre no programa àdireita, o valor 2147483647 é impresso em vez do 0.
#include <stdio.h>intmain(int nargs, char *args[]){int i;unsigned int ui;i = -1;ui = 2;if (i > ui){printf("negativo maior que positivo\n");
}else{printf("negativo não maior que positivo\n");
}}
#include <stdio.h>intmain(int nargs, char *args[]){int i, resultado;unsigned int ui;i = -1;ui = 2;resultado = i/ui;printf("%d\n", resultado);
}
A imposição de tipos também pode não funcionar quando o programa é compilado com otimização, em virtude dasregras de identificação (aliasing rules) usadas pelo compilador. No trecho a seguir a primeira impressão produz 11111111 ou 2222 2222, dependendo do nível de otimização utilizado na compilação. A última impressão pode resultarem 49 ou 50.
#include <stdio.h>intmain(int nargs, char *args[]){short vetor[2];vetor[0] = 0x1111;vetor[1] = 0x1111;
*(int *)vetor = 0x22222222;printf("%x %x\n", vetor[0], vetor[1]);
double a = 0.5;
11
double b = 0.01;double c = a / b;printf("%f / %f = %f\n", a, b, c);printf("%f / %f = %d\n", a, b, (int)c);
}
FONTE: [misra, 2-05-00], [nureg, 4.1.2.6].
Regra TD-6. Não use o mesmo identificador em múltiplas declarações.
RAZÃO: Evita erros de interpretação e resultados inesperados devido a conversões não pretendidas.COMENTÁRIO: Múltiplas declarações com tipos compatíveis tornam o programa confuso; com tipos incompatíveis, ocomportamento não é definido.EXEMPLO: O programa abaixo gera um resultado inesperado, decorrente da impressão de um vetor como se fosse uminteiro.
#include <stdio.h>int b;intmain(int nargs, char *args[]){printf("%d\n", b);
}
/* -------------------------------a variável b é declarada em outroarquivo como:--------------------------------- */
char b[4] = {’a’, ’b’, ’c’, ’d’}
FONTE: [misra, 2-02-10], [nureg, 4.1.2.6].
4 Memória
4.1 Alocação e atribuição de valores
Recomendação Mem-1. Minimize alocação dinâmica de memória.
RAZÃO: Reduz a ocorrência de problemas normalmente associados com a alocação dinâmica de memória.COMENTÁRIO: A alocação dinâmica de memória, embora torne os programas mais eficientes, pode causar problemassérios, exigindo atenção redobrada no desenvolvimento e manutenção dos programas. Alguns dos problemas:
• Alocar memória sem liberá-la após o uso.
• Tentativa de usar memória não alocada ou que já tenha sido liberada.
• Tamanho insuficiente de memória.
Também é possível que funções de alocação de memória exibam diferentes comportamentos dependendo do tipodos argumentos. Por exemplo, a função void *realloc(void *pv, size_t tam) possui diferentes com-portamentos:
• A função reduz o bloco de memória alocado, liberando o espaço desnecessário, se:
– se o novo tamanho é menor do que o antigo.
12
• A função aloca novo bloco de memória se:
– se o novo tamanho é maior do que o antigo e não pode ser alocado a partir de pv.
– pv é NULL e tam é maior que zero (funcionando como malloc(tam)), ou
• A função retorna NULL se:
– a alocação não pode ser realizada, ou
– se pv não é NULL e tam é zero (funcionando como free(pv)).
• A função tem comportamento indefinido se:
– pv não corresponde a um espaço previamente alocado por malloc, calloc, realloc, ou que já tenhasido liberado.
– pv é NULL e tam é zero.
FONTE: [gnu, 4.2], [misra], [nureg 4.1.1.1], [iso, 7.20.3.4].
Regra Mem-2. Use funções de memória que possibilitem a definição de limites de atuação.
RAZÃO: O uso de funções sem a verificação dos limites de atuação pode causar gravação em (ou acesso a) endereçosnão pretendidos.COMENTÁRIO: As seguintes funções agem sobre um número definido de caracteres:
char *strncat(char *, char *, size_t),int strncmp(char *, char *, size_t),int strncpy(char *, char *, size_t),void *memmove(char *, char *, size_t), evoid *memcpy(char *, char *, size_t).
As seguintes funções não permitem limitar a atuação e dependem, portanto, do conteúdo dos objetos que manipu-lam: strcat, strcmp e strcpy.EXEMPLO: Observe, entretanto, que mesmo funções que permitem a checagem dos limites de atuação podem oferecerriscos.
#include <stdio.h>#include <string.h>#define TAM1 (10)#define TAM2 (20)intmain(int nargs, char *args[]){char str1[TAM1], str2[TAM2], str3[TAM2];
strcpy(str1,"vt com dez"); /* ultrapassa str1 */strncpy(str1,"vt com dez", TAM1); /* Falta ’\0’ */strncpy(str1,"vt com dez", TAM1 - 1); /* Falta ’\0’ */strncpy(str2, str1, TAM2 + 5); /* Ultrapassa str2 */memmove(str1, str2, TAM2); /* ultrapassa str1 */memcpy(str1, str2, TAM2); /* ultrapassa str1 (indefinido) */return 0;
}
FONTE: [misra, 2-18-00-05], [nureg, 4.1.1.5, 4.1.1.6], [iso, 7.21].
13
Regra Mem-3. Inicie variáveis e ponteiros antes do uso.
RAZÃO: Favorece a legibilidade e facilita a manutenção.COMENTÁRIO: Variáveis automáticas não são iniciadas. Variáveis globais podem ou não ser iniciadas. As variáveisestáticas são sempre iniciadas, mas a iniciação explícita favorece a legibilidade.
• Inicie as variáveis locais no início da função.
• Inicie as variáveis globais uma única vez.
• Use um procedimento específico para iniciar as variáveis globais.
FONTE: [misra, 2-08-05-01], [nureg, 4.1.2.2].
4.2 Referência (ponteiros)
Regra Ptr-1. Teste os ponteiros para valores válidos antes do uso.
RAZÃO: Diminue a imprevisibilidade.COMENTÁRIO: Ponteiros não iniciados, ou com valores inválidos, são particularmente perigosos.EXEMPLO: O trecho de código à esquerda resulta em comportamento imprevisível.
#define VALOR (14L)long *ptr;...
*ptr = VALOR;...
#define VALOR (14L)long *ptr = NULL;long valor;...ptr = &valor;...if (ptr != NULL){
*ptr = VALOR;}
FONTE: [misra], [nureg, 4.1.2.2].
Regra Ptr-2. Só use um ponteiro para acessar elementos de vetor se ele tiver sido declarado como ponteiro para estevetor.
RAZÃO: Evita o acesso indevido a posições de memória que não sejam elementos de vetor.EXEMPLO: No código à esquerda a variável vet não é um ponteiro para vetor. Observe que nos dois códigos épossível chamar a função passando um ponteiro para um inteiro. Entretanto, o código à direita facilita a revisão, poisespecifica mais claramente a intenção do programador.
14
voidfuncao_desconforme(const int *vet){printf("%d\n", *(vet + 3));printf("%d\n", *(vet++));printf("%d\n", vet[2]);
}
voidfuncao_conforme(const int vet[]){printf("%d\n", *(vet + 3));printf("%d\n", *(vet++));printf("%d\n", vet[2]);
}
FONTE: [misra, 2-05-00-16], [nureg].
Regra Ptr-3. Só use aritmética de ponteiros com ponteiros que referenciam vetores ou elementos de vetores.
RAZÃO: A adição e subtração de ponteiros que não apontem para vetores resulta em comportamento indefinido.EXEMPLO: No código à esquerda a variável vet não é um ponteiro para vetor.
voidfuncao_desconforme(const int *vet){printf("%d\n", *(vet + 3));printf("%d\n", *(++vet));
}
voidfuncao_conforme(const int vet[]){printf("%d\n", *(vet + 3));printf("%d\n", *(++vet));
}
FONTE: [misra, 2-05-00-16], [nureg].
Regra Ptr-4. 04. Só realize operações entre ponteiros, aritméticas ou relacionais (<, >, <=, >=), se eles apontarempara o mesmo vetor.
RAZÃO: As operações que envolvem ponteiros que apontam para vetores diferentes resultam em comportamento in-definido.EXEMPLO: Os resultados das operações da função à esquerda são indefinidos.
voidfuncao_desconforme(const int ori[],
const int dst[]){int res;int *p1 = (ori + 1);int *p2 = (dst + 1);res = p1 - dst;res = ori - p2;res = p1 - p2;res = p2 > ori;res = ori >= p2;
}
voidfuncao_conforme(const int ori[],
const int dst[]){int res;int *p1 = (ori + 1);int *p2 = (dst + 1);res = p1 - ori;res = p2 - dst;res = p1 - p1;res = p1 > origem;res = dst >= p2;
}
15
FONTE: [misra, 2-05-00-17], [nureg].
Regra Ptr-5. Use índices em vez de ponteiros para acessar elementos de vetores.
RAZÃO: Favorece o entendimento, facilita a manutenção.EXEMPLO: O código à direita é mais claro que o equivalente à esquerda.
voidfuncao_desconforme(const int vet[]){printf("%d\n", *(vet + 3));printf("%d\n", *(vet + 1));printf("%d\n", *vet);
}
voidfuncao_conforme(const int vet[]){printf("%d\n", vet[3]);printf("%d\n", vet[1]);printf("%d\n", vet[0]);
}
FONTE: [misra, 2-05-00-15], [nureg].
Regra Ptr-6. Use ponteiros ao declarar grandes estruturas como argumentos em chamadas de função.
RAZÃO: Diminui o acoplamento e previne estouro da pilha de chamada (stack overflow).COMENTÁRIO: Em C/C++ vetores (arrays) são sempre passados por referência (usando ponteiros), mas estruturas euniões podem ser passadas por referência ou como valores da pilha de chamada (stack).EXEMPLO: O programa à esquerda terá a pilha de execução estourada bem antes que o da direita.
#include <stdio.h>#include <stdlib.h>
typedef struct grande grande_t;struct grande{long int qtd;long int valores[5000];
};void teste(grande_t);intmain(int nargs, char *args[]){grande_t estrutura;estrutura.qtd = 0;teste(estrutura);return 0;
}voidteste(grande_t estrutura){estrutura.qtd++;printf("%d ", estrutura.qtd);teste(estrutura);
}
#include <stdio.h>#include <stdlib.h>
typedef struct grande grande_t;struct grande{long int qtd;long int valores[5000];
};void teste(grande_t *);intmain(int nargs, char *args[]){grande_t estrutura;estrutura.qtd = 0;teste(&estrutura);return 0;
}voidteste(grande_t *estrutura){estrutura->qtd++;printf("%d ", estrutura->qtd);teste(estrutura);
}
16
FONTE: [misra], [nureg, 4.1.1.3], iso[6.7.5.3].
5 Fluxo de controle
Recomendação Ctr-1. Minimize a complexidade do fluxo de controle.
RAZÃO: Quanto mais complexo é o fluxo de controle, mais difícil é o desenvolvimento e a manutenção.COMENTÁRIO: Em geral deve-se priorizar o entendimento e a legibilidade:
• Use switch em vez de if’s aninhados.
• Use cláusula default no comando switch e cláusula else no comando if.
FONTE: [misra, 2-06-04], [nureg, 4.1.2.2].
Regra Ctr-2. Um programa não deve conter elementos não utilizados, caminhos intransitáveis, código morto ou nãorealizável.
RAZÃO: Estas situações indicam ou um esquecimento por parte do programador ou um erro de lógica.COMENTÁRIO: Um código não realizável nunca será executado, por construção. Um caminho intransitável existesintaticamente mas a semântica da aplicação faz com que nunca seja executado. Um código morto é aquele cujaexecução não afeta o processamento.EXEMPLO:
voidexem(int a){int b = 34; // elemento não usadoswitch (a - a){printf("codigo nao realizavel\n");
case ’1’:printf("opcao 1\n"); // caminho intransitávelbreak;
default:a = a + 2; // código mortoprintf("opcao padrao\n");break;
}return;printf("codigo nao realizavel");
}
FONTE: [misra, 2-00-01], [nureg, 4.1.2.2].
Regra Ctr-3. O valor retornado por uma expressão que não tenha efeitos colaterais, ou por uma função cujo valorde retorno seja void, deve ser utilizado.
RAZÃO: Se um valor não é utilizado sua produção não precisa existir.COMENTÁRIO: No caso de funções, se a chamada é feita por conta dos efeitos colaterais, o correto é codificar essesefeitos em um procedimento (função sem valor de retorno).
17
Pode-se, em alguma situações, descartar explicitamente o valor de retorno, impondo o valor void, como, porexemplo, em (void)funcao(3).FONTE: [misra, 2-00-01-07], [nureg].
Regra Ctr-4. Não use funções com um número variável de argumentos.
RAZÃO: Argumentos variáveis dificultam a manutenção e verificação da função.COMENTÁRIO: Funções com número variável de argumentos são difíceis de serem verificadas. A chamada a funçõescom número variável de argumentos e cujo protótipo não tenha uma lista de parâmetros terminada em reticências,produz um comportamento não especificado.EXEMPLO:
#include <stdio.h>#include <stdarg.h>
void aux(int, char, ...);intmain(int nargs, char *args[]){aux(1, ’x’);aux(5, ’t’, ’e’, ’s’, ’t’, ’e’);
}
voidaux(int a, char b, ...){va_list ptr;va_start(ptr, b);printf("%c", b);while (--a > 0){b = va_arg(ptr, int);printf("%c", b);
}printf("\n");va_end(ptr);
}
FONTE: [misra, 2-08-04-01], [nureg, 4.2.1.5].
Regra Ctr-5. Não use expressões como argumentos em chamadas a funções ou macros.
RAZÃO: O uso de expressões pode produzir comportamento não especificado ou indesejado.COMENTÁRIO: O ordem de avaliação dos argumentos não é determinada. Em particular os operadores unários ++ e-- são problemáticos.EXEMPLO: Os trechos à esquerda podem produzir resultados inesperados.
Versão errada Versão correta
funcao(i, i++); a = i; i++;funcao(a, i);
#define MAX(x, y) ((x > y) ? x : y)...limite = MAX(++i, j);
#define MAX(x, y) ((x > y) ? x : y)...++i;limite = MAX(i, j);
18
FONTE: [misra], [nureg, 4.1.2.5].
6 Erros e exceções
Regra Err-1. Assegure que as expressões aritméticas produzirão os resultados com a acurácia e precisão esperadas.
RAZÃO: Erros de precisão e acurácia devido a conversões de tipos e imprecisão na representação dos valores e opera-ções com comportamento não definidos são comuns.COMENTÁRIO: As operações aritméticas, em particular as de ponto flutuante, são naturalmente imprecisas. Deve-se:
• Usar o tipo double.
• Evitar a comparação de igualdade com valores de tipo flutuante.
• Garantir que o valores resultantes de imposição de tipos podem ser representados no tipo alvo.
• Controlar o truncamento das divisões inteiras.
• Controlar a ordem de precedência usando parêntesis.
• Assegurar que o valor da expressão não depende da ordem de avaliação.
• Separar atribuição e avaliação
FONTE: [misra, 2-00-03, 2-00-04, 2-01-00], [nureg, 4.1.2.7, 4.1.2.8].
Regra Err-2. Verifique o resultado de operações que podem resultar em erros ou exceções.
RAZÃO: Evita a ocorrência de comportamento imprevisível ou inesperado.COMENTÁRIO: Operações de entrada e saída, operações que utilizam ponteiros, operações matemáticas, etc. podemresultar em erros que devem ser verificados.EXEMPLO:
#include <stdio.h>intmain(int nargs, char *args[]){FILE *fp;fp = (FILE *)NULL; /* inicie o ponteiro */fp = fopen("id_arquivo","r"); /* atribua valor */if (fp != (FILE *)NULL) /* verifique validade */{/* ... */
}if (fp != (FILE *)NULL) /* verifique validade */{close(fp);fp = (FILE *)NULL; /* libere o ponteiro */
}return 0;
}
19
FONTE: [misra, 2-00-03], [nureg, 4.2.2.1].
Regra Err-3. Verifique a ocorrência de erros de domínio e de imagem.
RAZÃO: Evita resultados errados.COMENTÁRIO: Os erros de domínio ocorrem quando os dados de entrada estão fora da faixa de representação definidapara a operação. Os erros de imagem ocorrem quando os dados de saída estão fora da faixa de representação dosvalores que podem ser produzidos. As chamadas a funções da biblioteca padrão podem produzir estes tipos de erros,que devem ser verificados através da variável errno.
#include <stdio.h>#include <math.h>#define PI (3.14159)intmain(int nargs, char *args[]){double x = 4, res;res = acos(x);if ((res >= 0) && (res <= PI/2)){printf("quadrante I\n");
}else{printf("quadrantes "
"II, III ou IV\n");}
return 0;}
#include <stdio.h>#include <math.h>#include <errno.h>#define PI (3.14159)intmain(int nargs, char *args[]){double x = 4, res;res = acos(x);if (errno == EDOM){printf("Erro de dominio\n");
}else{if ((res >= 0) && (res <= PI/2)){printf("quadrante I\n");
}else{printf("quadrantes "
"II, III ou IV\n");}
}return 0;
}
OBSERVAÇÕES: O padrão MISRA (2-19-03-01) determina o não uso de errno.FONTE: [misra, 2-00-03, 2-19-03-01], [nureg, 4.2.2.1], [iso, 7.12].
Recomendação Err-4. Use funções-envelope para realizar verificação não realizada pelas funções da bibliotecapadrão.
RAZÃO: Minimiza o risco de erros ou comportamento indefinido.COMENTÁRIO: As funções-envelope (wrapping functions) são uma forma conveniente de encapsular a verificação desituações não detectadas pelas funções da biblioteca padrão.FONTE: [misra, 2-00-03], [nureg, 4.1.1.8].
20
Recomendação Err-5. Teste a validade dos argumentos de entrada ao iniciar e a validade dos argumentos de saídaao finalizar a execução de cada função.
RAZÃO: Evita erros que podem comprometer a integridade do sistema.COMENTÁRIO: A instrumentação assegura que a função receberá e produzirá apenas os valores esperados.FONTE: [misra, 2-00-03, 2-08-04-06], [nureg, 4.1.2.5].
Regra Err-6. Trate erros e exceções localmente.
RAZÃO: O tratamento local de erros facilita a interpretação das causas e a manutenção do programa.COMENTÁRIO: A propagação de erros através de vários níveis de chamadas pode ocultar a natureza da falha, dificul-tando a interpretação do que ocorreu.
É aceitável a existência de um procedimento unificado para tratamento de erros semelhantes que podem ocorrerem vários pontos do programa.FONTE: [misra], [nureg, 4.2.2.1].
Referências
[nureg] Hecht, H., M. Hecht, S. Graff, W. Green, D. Lin, S. Koch, A. Tai and D. Wendelboe. Review Guideli-nes on Software Languages for Use in Nuclear Power Plant Safety Systems, Technical Report, NUREG CR-6463, US Nuclear Regulatory Commission, 1996. (www.nrc.gov/ about-nrc/ regulatory/ research/ digital/ tech-reference.html)
[iso] ISO. C Programming Language Standard, ISO/IEC 9899:1999, Joint Technical Committee ISO/IEC JTC 1,Information technology, Committee Draft, 2005. (www.open-std.org/ jtc1/ sc22/ wg14)
[misra] MISRA. Misra C++: Guidelines for the use of C++ language in critical systems, Draft for comment, TheMotor Industry Software Reliability Association, 2007. (www.misra.org.uk)
[gnu] Stallman, Richard, et al. GNU Coding Standards, GNU Project, Free Software Foundation, October, 2007.(www.gnu.org/ prep/ standards)
21