Linguagens e Ambientes de Programação (2022/2023)



Teórica 16 (03/mai/2023)

Bibliotecas. Biblioteca padrão do C.
Funções com número variável de argumentos.
Tratamento de erros.
Input/Output.
Strings.
Output formatado.
Input formatado.



Bibliotecas

Em diferentes linguagens de programação, existem diferentes filosofias relativamente à inclusão de funcionalidades específicas na linguagem.

Há linguagens de programação onde as funcionalidades específicas para manipular strings, ficheiros, etc. estão disponíveis ao nível da própria linguagem e não em bibliotecas externas. São os casos das linguagens: Fortran, Cobol e Pascal, por exemplo.

Pelo contrário, noutra linguagens essas funcionalidades estão disponíveis em bibliotecas externas e não ao nível da linguagem. São o caso das linguagens: Java, OCaml, C, C++, etc.

Esta aula vai ser integralmente dedicada a estudar algumas das funcionalidades da linguagem C que foram remetidas para a sua biblioteca.

Biblioteca padrão

No caso do C, existe uma pequena biblioteca padronizada, conhecida por libc.

A funcionalidade da biblioteca padrão está disponível através de perto de três dezenas ficheiros de interface, dos quais destacamos os seguintes: <ctype.h>, <limits.h>, <locale.h>, <math.h>, <setjmp.h>. <signal.h>, <stdarg.h>, <stdbool.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <thread.h>.

A aula de hoje envolve temas que permitem a exploração de parte das funcionalidades que estão disponíveis na biblioteca padrão do C.

Outras bibliotecas

A biblioteca padrão do C é muito útil, mas é relativamente limitada (mais limitada do que a do Java, por exemplo). Por isso alguns programas em C costumam recorrer a outras bibliotecas (tipicamente bibliotecas de código livre), tais como: Num sistema Linux observe o conteúdo das diretorias /lib e especialmente /usr/lib para ver o enorme número de bibliotecas disponível. Os ficheiros de interface correspondentes a todas essas bibliotecas são geralmente guardados na diretoria /usr/include.

Usar bibliotecas

Para usar uma biblioteca é preciso primeiro incluir um ou mais ficheiros de interface no código fonte. Por exemplo:

Depois de escrito o programa, na altura de compilar é preciso indicar quais as bibliotecas a ligar. Usa-se para isso a opção -l do compilador. Exemplo:

Algumas funções de biblioteca

Apresentamos algumas das funções da biblioteca padrão do C. No Linux, o comando man permite obter a documentação sobre qualquer uma destas funções (desde que o pacote "manpages-dev" esteja instalado).

Input/Output

Essas operações funcionam com ficheiros de texto e ficheiros binários, exceto as últimas quatro operações que funcionam apenas com ficheiros de texto.

Matemática

Strings

Memória


Funções com número variável de argumentos

Vamos adicionar ao módulo LinkedList uma função com número variável de argumentos para criar listas novas. A função tem de ter pelo menos um argumento com nome (neste caso n) com informação sobre os argumentos que se seguem (neste exemplo, n indica o número de argumentos); os argumentos anónimos são representados por .... Pode ser testada usando: Os argumentos anónimos não podem ser validados no ponto da chamada. Se, na chamada, forem passados argumentos a mais, os argumentos em excesso são ignorados pela função. Se forem passados argumentos a menos, ou argumentos de tipo errado alguns argumentos da função conterão valores indefinidos.

Para aprender mais sobre funções com número variável de argumentos, usar o comando man stdarg.


Tratamento de erros

Tratamento de erros gerados ao nível do sistema operativo ou das bibliotecas

Na linguagem C, muitas das chamadas a funções do sistema operativo ou a chamadas de funções de biblioteca retornam um valor especial a informar que um erro ocorreu. Esse valor deve ser testado e medidas apropriadas devem ser tomadas.

Alguns exemplos:

Depois de detetado o erro é possível obter mais informação sobre este usando o módulo da biblioteca padrão errno. Ao incluirmos o ficheiro de interface <errno.h> ganhamos acesso a uma variável inteira chamada errno. Após um erro de sistema, essa variável contém sempre um código que indica qual a razão exata do erro. O valor 0 significa que não há erro. Exemplo: Este esquema de tratamento de erros é bastante mau pois obriga a misturar o tratamento de erros com a lógica normal do programa.

No exemplo anterior, tentou-se organizar bem o tratamento dos erros, mas mesmo assim restou o problema de ter ficado estabelecido que um erro causa a terminação imediata do programa. Quem chama a função openFile não tem a opção de atuar de outra maneira.

Saltos não-locais

A linguagem C não dispõe de qualquer mecanismo de alto-nível para tratamento de exceções. Contudo a biblioteca padrão contém um módulo chamado setjmp que fornece um mecanismo de saltos não locais que permite tratar exceções a baixo nível.

O módulo setjmp disponibiliza as funções setjmp e longjmp:

Comparando com o OCaml, repare que em C a função longjmp desempenha o mesmo papel da expressão raise. A função setjmp, juntamente com o switch envolvente, desempenha o mesmo papel da expressão try-with.

Tratamento de erros gerados ao nível do hardware

Para apanhar erros do género divisão-por-zero, a aplicação tem de instalar rotinas de serviço para detetar determinadas interrupções geradas pelo hardware. Para isso é necessário usar os serviços do módulo signal da biblioteca padrão.

O seguinte exemplo ilustra a utilização da função signal e apanha todas as interrupções geradas a partir do teclado usando CTRL-C.

É possível fazer um longjmp para fora duma rotina de serviço de interrupções? O padrão não dá garantias sobre o assunto e geralmente não funciona mesmo.

Em muitas implementações de C (e.g. GCC) o módulo setjmp inclui funções extra (sigsetjmp e siglongjmp) que permitem fazer isso. Mas esta funcionalidade não faz parte do padrão da biblioteca C. Faz sim parte do padrão POSIX para bibliotecas de acesso aos serviços do sistema operativo.


Input/Output

Exemplo 1 - Cópia de ficheiro (de texto ou binário) byte a byte.

Note que estamos a abrir os dois ficheiros usando um modo "b" especial que indica processamento de dados binários e não de texto. Num processamento em modo "texto", podem ocorrer conversões de caracteres especiais. Por exemplo, no Windows, em modo "texto", a escrita de "\n" gera "\r\n" no ficheiro e a leitura de "\r\n" resulta num simples "\n".

Exemplo 2 - Cópia de ficheiro de texto linha a linha.

Repare que aqui deixámos de usar o "b".

Exemplo 3 - Cópia de ficheiro (de texto ou binário) em blocos de 1024 bytes.

Aqui usa-se o "b".


Strings

Na linguagem C, as strings são simples vetores de carateres e cada string é terminada pelo caráter nulo, que se escreve '\0'. Na seguinte inicialização de variável, a string tem um comprimento nominal de 3, mas internamente ocupa 4 bytes, por causa do terminador. O facto das strings terem todas uma marca de fim, faz com elas sejam muito flexíveis e práticas de usar. A sua flexibilidade é equivalente à dos vetores acompanhados, pois o programa pode controlar o comprimento duma string.

Um detalhe técnico: no exemplo anterior, a variável str fica apontar diretamente para uma zona de memória constante, onde o sistema coloca todos os literais de tipo string. Não é permitido alterar o valor dum literal.

Contudo, escrevendo assim

a linguagem reserva espaço para a nova variável e copia para lá a string. Neste caso existe liberdade de alterar a cópia.

Funções sobre strings

Para exemplificar a manipulação de strings, eis uma função que conta o número de ocorrências dum caráter numa string. Examine com atenção o ciclo for, porque este é típico das funções que manipulam strings. A chamada count("hello",'l') produz o resultado 2.

A seguinte função acrescente um caráter no final duma string, assumindo que há espaço na string:

Eis outra forma de programar as mesmas duas funções, desta vez usando apontadores em vez de indexação:

A biblioteca padrão 'string'

A seguinte diretiva no início do nosso programa permite ganhar acesso a numerosas funções predefinidas de manipulação de strings. Eis as funções de biblioteca mais usadas: O seguinte código copia uma string: Este código tem o mesmo efeito:

Output formatado

O output formatado em C é gerado usando funções da família printf. A função original escreve o output no canal de saída padrão, mas há uma variante que escreve num ficheiro de saída qualquer e outra variante que escreve numa string. Estas funções aceitam um número variável de argumentos. A string de formatação é quase toda copiada literalmente para o output. A exceção são os especificadores de formato, começados pelo caráter especial '%', que provocam a escrita dos parâmetros que estiverem colocados após a strings de formatação. Eis um exemplo, seguido do respetivo output: A função printf retorna o número de carateres escritos. Em caso de erro retorna um valor negativo.

printf em C++

Em C++ existem outras primitivas de output baseadas no operador "<<", mas a operação printf também está disponível.

printf em Java

A operação printf é tão popular, que foi incorporada na biblioteca do Java. Em Java, os especificadores de formato são ainda em maior número do no C.

printf em OCaml

A operação printf também está disponível na biblioteca do OCaml:

Input formatado

O input formatado em C é lido usando funções da família scanf. A função original lê o input no canal de entrada padrão, mas há uma variante que lê num ficheiro de entrada qualquer e outra variante que lê duma string. Estas funções aceitam um número variável de argumentos. A string de formatação é usada de forma literal (forma exata) para fazer emparelhamento com o input, mas há duas exceções: (1) os carateres brancos (' ', '\t', '\n') emparelham com qualquer sequência de brancos; (2) os especificadores de formato, começados pelo caráter especial '%', causam a leitura de valores para os argumentos que estiverem colocados após a string de formatação.

Note que todos os argumentos que se seguem à string de formatação são argumentos de saída, sendo portanto implementados usando apontadores. Eis um exemplo:

A função scanf retorna o número de parâmetros lidos com sucesso; portanto, em caso de falhanço de emparelhamento, esse valor pode ser inferior ao esperado. Antes do primeiro argumento ter sido lido, caso o input termine subitamente durante emparelhamento que esteja a ser bem sucedido, então é retornado o valor EOF.

scanf em C++

Em C++ existem outras primitivas de input baseadas no operador ">>", mas a operação scanf também está disponível.

scanf não existe em Java

A operação scanf não existe na biblioteca do Java, mas as classes Scanner e Pattern oferecem uma solução ainda mais complete e sofisticada.

scanf em OCaml

A operação scanf também está disponível na biblioteca do OCaml:

Vídeos antigos

Estes vídeos poderão estar um pouco desatualizados, pois foram feitos no contexto duma edição anterior do LAP. Contudo, partes dos vídeos poderão continuar a ter alguma utilidade.

#--- 40 50