12. Estrutura de um programa em C

Um programa em C é organizado como uma sequência de funções e variáveis.

As funções não podem conter outras funções mas podem conter variáveis.

Qualquer variável ou função deve ser declarada antes de ser invocada.

O ponto de entrada de execução num programa é a função main (em certas circunstâncias pode ter outro nome).

A linguagem C dispõe de uma biblioteca normalizada, essencialmente contendo código de funções.

12.1. Variáveis

As variáveis definidas no interior de funções chamam-se variáveis locais. As outras chamam-se variáveis globais e podem ser acedidas por todas as funções. As variáveis locais também têm a designação de variáveis automaticas. São as declaradas no âmbito de um bloco (abrir e fechar chavetas). Têm existência temporária – são criadas durante a execução do bloco e descartadas à saída do bloco.

 1   int x = 10;
 2
 3   int f(char c)
 4   {
 5     int g = ' ';
 6     return c + g;
 7   }
 8
 9   int main()
10   {
11      char y = 'a';
12      if (x == 2) {
13         bool z = true;
14      }
15      f(y);
16   }

No exemplo, a variável x é global, y e z são locais. A variável z só tem existência enquanto o processamento estiver a decorrer entre as linhas 12 e 13. A variável y tem existência enquanto o processamento estiver a decorrer entre as linhas 10 e 16.

As variáveis são também classificadas como variáveis estáticas ou automáticas. As automáticas são as variáveis locais que são criadas e eliminadas à medida que a execução do programa entra e sai do âmbito do bloco. As variáveis estáticas são aquelas que estão permanentemente alocadas em memória – têm um tempo de vida igual ao do programa. É o caso das variáveis globais.

As variáveis locais, há dos dois tipos, as estáticas e as automáticas. As estáticas são assinaladas com a colocação da palavra static antes da definição.

void f()
{
    static int i;
    int j;
}

No exemplo acima, i é uma variável estática tem uma localização em memória permanenete e exclusiva, como as variáveis globais. É criada no arranque do programa, inicializada com zero. A variável j, é criada e eliminada à entrada e à saída da função f, com valor inicial indefinido.

12.2. Exemplo de programa

O objetivo do programa é contar as ocorrências das vogais em frases. É baseado na função histogram_vowel que recebe a frase a analisar e um array para recolher as contagens das vogais. Esta função tem a seguinte declaração:

void histogram_vowel(char phrase[], uint16_t max_letters, uint16_t occurrences[5])

O primeiro parâmetro – phrase – representa um array de caracteres com a frase a processar. O segundo parâmetro – max_letters –, inteiro natural representado a 16 bits, indica o número máximo de caracteres a processar. O terceiro parâmetro – occurrences – é um array com cinco posições de inteiros naturais, em que cada posição recebe a contagem de cada uma das vogais.

Listagem 12.1 Programa para contar vogais
 1char phrase1[] = "aeiou";
 2char phrase2[] = "a ee iii oooo uuuuu";
 3
 4uint16_t occurrences1[SIZE];
 5uint16_t occurrences2[SIZE];
 6uint16_t occurrences3[SIZE];
 7
 8int main(void)
 9{
10	histogram_vowel(phrase1, 15, occurrences1);
11	histogram_vowel(phrase2, 19, occurrences2);
12	histogram_vowel("Hello world", 7, occurrences3);
13}

Na Listagem 12.1, nas linhas 1 a 3, definem-se três frases. São arrays de caracteres, inicializados com o código dos caracteres que compõem as strings, cujas dimensões são iguais às dimensões das strings mais um – o terminador '\0'. Os arrays phrase1 e phrase2 têm a dimensão vinte e o array phrase3 tem a dimensão seis.

Nas linhas 5 a 7, definem-se três array para receberem as contagens. Os elementos destes arrays são do tipo inteiro natural, representados a 16 bits (uint16_t).

Em linguagem C, as variáveis de localização estática sem valor inicial explícito, são inicializadas com zero. É o caso destes três arrays, ambos, e todas as suas posições, são inicializadas com o valor zero.

O símbolo SIZE, que aparece na definição da dimensão destes arrays, é uma macro definida de seguinte forma:

#define SIZE 5

Nas linhas 11 a 13, invoca-se a função histogram_vowel para cada uma das frases. Na linha 11, é invocada com o array phrase1 como primeiro argumento, o valor 18 como número máximo da caracteres a processar e por último o array ocurrencies1.

Listagem 12.2 Definição da função histogram_vowel
1void histogram_vowel(char phrase[], uint16_t max_letters, uint16_t occurrences[5])
2{
3	for (uint16_t i = 0; phrase[i] != '\0' && i < max_letters; i++) {
4		int16_t idx;
5		if ((idx = find_vowel(phrase[i])) != -1)
6			occurrences[idx]++;
7	}
8}

O corpo principal da função histogram_vowel consiste em iterar sobre as posições do array phrase, a começar na primeira posição – uint16_t i = 0 – até se encontrar o terminador da stringphrase[i] != '\0' – ou se atingir o máximo de iterações – i < max_letters.

Em cada iteração, é invocada a função find_vowel, que recebe o código de uma letra e a classifica. A classificação é retornada como valor da função, na forma de um inteiro relativo, representado a 16 bits. Se a letra for uma vogal, é retornado o valor 0 para 'a', 1 para 'e' e assim sucessivamente até, 4 para 'u'. Se a letra não for uma vogal, é retornado -1.

int16_t find_vowel(char letter)

A invocação de find_vowel está embebida na expressão de afetação idx = find_vowel(phrase[i]), cujo valor é o valor retornado por find_vowel. Esse valor é afetado a idx e depois comparado com a constante literal -1 (linha 5). Se for diferente é contabilizada a ocorrência de uma vogal (linha 6).

A indicação [5] na declaração do parâmetro uint16_t occurrences[5] é meramente ilustrativa. Não tem qualquer consequência no funcionamento do programa. Essa declaração é equivalente a uint16_t occurrences[] (sem indicação de dimensão).

A única informação passada como argumento num parâmetro do tipo array, como phrase ou occurrences, é o local da memória onde estão alojados os arrays.

A sub-expressão ocurrencies[idx] dá acesso direto à posição do array utilizado como argumento de histogram_vowel. Na invocação da linha 10 na Listagem 12.1, é o array occurrencies1. O valor de idx situa-se entre 0 e 4. O array utilizado como argumento deve ter pelo menos 5 posições.

Na compilação de uma função, o compilador da linguagem C não realiza qualquer verificação da ultrapassagem dos limites de um array passado em parâmetro, nem ativa qualquer mecanismo para verificação em execução. Na hipótese de o array passado como argumento em occurrences ter uma dimensão inferior a 5, uma escrita na posição 4 consumaria um acesso inválido à memória, com efeito imprevisível.

Poder-se-ão alvitrar alguns efeitos:
  • o endereço de memória acedido é viável e não está atribuido a outra variável – o programa tem um comportamento «normal»;

  • o endereço de memória acedido não é viável – o programa poderá terminar abruptamente ou o acesso ser ignorado;

  • o endereço de memória acedido está atribuido a outra variável – o efeito notar-se-á mais tarde, com comportamentos incompreensiveis no contexto de utilização dessa variável.

Listagem 12.3 Definição da função find_vowel (a)
 1int16_t find_vowel(char letter)
 2{
 3	switch (letter) {
 4		case 'a':
 5			return 0;
 6		case 'e':
 7			return 1;
 8		case 'i':
 9			return 2;
10		case 'o':
11			return 3;
12		case 'u':
13			return 4;
14		default:
15			return -1;
16	}
17}
Listagem 12.4 Definição da função find_vowel (b)
 1int16_t find_vowel(char letter)
 2{
 3	int16_t result = -1;
 4	switch (letter) {
 5		case 'a':
 6			result = 0;
 7			break;
 8		case 'e':
 9			result = 1;
10			break;
11		case 'i':
12			result = 2;
13			break;
14		case 'o':
15			result = 3;
16			break;
17		case 'u':
18			result = 4;
19	}
20	return result;
21}

Nas Listagem 12.3 e Listagem 12.4 a função find_vowel baseia-se na utilização de uma estrutura switch/case.

As notações de caracteres entre plicas ' ', representam o código numérico do caractere indicado. Por exemplo, 'a' origina o número 97, se se utilizar a codificação ASCII. Conforme o valor numérico em letter, a construção switch realiza um salto para a linha case com valor numérico igual.

Na Listagem 12.3, no caso do valor numérico de letter não estar contemplado em nenhum case o salto dá-se para default. Em qualquer caso é executado imediatamente o retorno da função com o valor de classificação como argumento de return.

Na Listagem 12.4, aplica-se o princípio «uma entrada - uma saída». A variável result é inicialmente afetada com -1, o valor que representa «não vogal», depois será afetada com a classificação adequada no respetivo case, se for vogal. A quebra break impede que o processamento siga para os casos seguintes. No caso de letter não ser uma vogal a estrutura de switch não realiza qualquer processamento.

Exercício: Programar uma versão da função find_vowel que seja indiferente a letras maiúsculas e a minúsculas.

Código completo: vowels.c