O que é uma asserção?

Uma asserção é uma condição que se deve sempre verificar-se num determinado ponto do programa.

Asserções em C

#include <assert.h>

assert( int cond ); 
prog: some_file.c:16: some_func: Assertion ... failed.

Exemplo

#include <assert.h>

int main(void) {
   int x = 5, y = -3;
   assert( x + y == 2 ); 
   // esta asserção é válida
   y = x;
   assert( x + y == 2 );
   // esta asserção já não é válida;
   // o programa termina imediatamente!
   printf("fim\n"); 
}

Porquê usar asserções

Pré-condições

Exprimir uma pré-condição pode ajudar a detetar erros na chamada de uma função.

Exemplo: anos bissextos

Testar se um ano é bissexto (exercício 4.6):

Esta correção ao calendário foi introduzida pelo papa Gregório XIII em 1582.

Logo: as regras acima só se aplicam a partir dessa data.

https://pt.wikipedia.org/wiki/Calend%C3%A1rio_gregoriano).

Testar bissextos

int bissexto(int n) {
   // n é o ano que queremos testar;
   // pré-condição: deve ser posterior a 1582 
   assert(n >= 1582); 
   // OK, vamos considerar os casos acima
   if (n%400 == 0) 
       return TRUE;
   if (n%4 == 0 && n%100 != 0) 
       return TRUE;
   return FALSE;
}

int main(void) {
  ...
}

Exemplo de execução

Introduza um ano: 2000
2000 foi bissexto

Introduza um ano: 1384
bissexto: ...: Assertion `n >= 1582' failed.

Outro exemplo

Calcular a pontuação do SCRABBLE para uma cadeia de letras (Exercício 7.6):

Pré-condição: a cadeia deve conter apenas letras maiúsculas 'A', 'B', ..., 'Z'.

Pontuação SCRABBLE

int scrabble(char str[]) {
    // pré-condição: apenas letras maiúsculas
    for (int i=0; str[i]!='\0'; i++) {
       assert(str[i]>='A' && str[i]<='Z');
    }
    // OK, vamos calcular a pontuação
    ...
}

Pós-condições

Exprimir uma pós-condição pode ajudar a detetar erros na implementação de uma função.

Exemplo: gerar pseudo-aleatórios

Gerar um número inteiro pseudo-aleatório ímpar entre 1 e 99 (última alínea do exercício 6.1).

int gerador(void) {
   int n;
   n = rand()%100 + 1;  // NB: ERRADO!
   // pós-condição: o resultado está no
   // intervalo esperado e é ímpar 
   assert(n>=1 && n<=99 && n%2 == 1);
   return n;
}

Testar o gerador

Vamos testar um gerador efetuando várias experiências:

int main(void) {
   for(int i = 0; i<1000; i++) {
      int n = gerador();
      printf("%d\n", n);
   }
}

A asserção falha na execução: detetamos um erro no gerador!

Corrigir o erro

Podemos separar as asserções para compreender o que falha:

   assert(n >= 1); 
   assert(n <= 99);
   assert(n%2 == 1);

A primeira asserção passa sempre mas a segunda e terceira podem falhar!

Ou seja:

(Exercicio: corrigir o gerador.)

Como usar pós-condições

Invariantes

Exemplo: eliminar repetidos

   int elimrep(int v[], int n);

Asserções de invariantes

int elimrep(int vec[], int n) {
   int k = 0;
   for(int i = 0; i < n; i++) {
      assert(0 <= k && k <= i);
      assert(0 <= i && i < n);
      int val = vec[i];
      if(!ocorre(vec, k, val)) {
         vec[k++] = val;
            // valor não repetido
      }
   }
   return k;
}

Porquê usar invariantes

Asserções e erros

Distinguir entre:

Erro de programação

uma situação que não deve ocorrer; por exemplo: usar um índice inválido ou dividir por zero.

Erro de execução

uma situação que pode ocorrer e que devemos tratar; por exemplo: um input incompleto do utilizador.

Asserções são um mecanismo para detetar o primeiro tipo de erros — não para tratar os segundos.