Instituto Federal de Santa Catarina - Câmpus Chapecó Ensino Médio Integrado em Informática - Módulo IV Unidade Curricular: Programação Estruturada Professora: Lara Popov Zambiasi Bazzi Oberderfer Plano de Aula Segunda-feira – Tarde – 26/05/2014 Objetivo: • Introduzir o conceito de alocação dinâmica na memória e registros / structs. Metodologia: • Explicar junto com a turma o desenvolvimento dos seguintes exercícios (a). • Após cada exercício, os alunos poderão treinar com um exercício semelhante (b). 1. Alocação Dinâmica na Memória As declarações abaixo alocam memória para diversas variáveis. A alocação é estática, pois acontece antes que o programa comece a ser executado: char c; int i; int v[10]; Às vezes, a quantidade de memória a alocar só se torna conhecida durante a execução do programa. Para lidar com essa situação é preciso recorrer à alocação dinâmica de memória. A alocação dinâmica é gerenciada pelas funções malloc e free, que estão na biblioteca stdlib. Para usar esta biblioteca, é preciso dizer #include <stdlib.h> no início do programa. Função malloc A função malloc (abreviatura de memory allocation) aloca um bloco de bytes consecutivos na memória do computador e devolve o endereço desse bloco. O número de bytes é especificado no argumento da função. No seguinte fragmento de código, malloc aloca 1 byte: char *ptr; ptr = malloc( 1); scanf( "%c", ptr); O endereço devolvido por malloc é do tipo "genérico" void *. O programador armazena esse endereço num ponteiro de tipo apropriado. No exemplo acima, o endereço é armazenado num ponteiro-para-char. Para alocar um tipo-de-dado que ocupa vários bytes, é preciso recorrer ao operador sizeof, que diz quantos bytes o tipo especificado tem: typedef struct { int dia, mes, ano; } data; data *d; d = malloc( sizeof (data)); d->dia = 31; d->mes = 12; d->ano = 2008; [As aparências enganam: sizeof não é uma função.] Overhead. Cada invocação de malloc aloca um bloco de bytes consecutivos maior que o solicitado: os bytes adicionais são usados para guardar informações administrativas sobre o bloco de bytes (essas informações permitem que o bloco seja corretamente desalocado, 1 mais tarde, pela função free). O número de bytes adicionais pode ser grande, mas não depende do número de bytes solicitado no argumento de malloc. Não é recomendável, portanto, invocar malloc repetidas vezes com argumento muito pequeno. É preferível alocar um grande bloco de bytes e retirar pequenas porções desse bloco na medida do necessário. Exercícios 1. Verificação do tamanho. Compile e execute o seguinte programa: typedef struct { int dia, mes, ano; } data; int main( void) { printf( "sizeof (data) = %d\n", sizeof (data)); printf( "sizeof (data *) = %d\n", sizeof (data *)); return 0; } A memória é finita Se a memória do computador já estiver toda ocupada, malloc não consegue alocar mais espaço e devolve NULL. Convém verificar essa possibilidade antes de prosseguir: ptr = malloc( sizeof (data)); if (ptr == NULL) { printf( "Socorro! malloc devolveu NULL!\n"); exit( EXIT_FAILURE); } A codificação frequente e repetida desse teste é cansativa para o programador e desvia a atenção do leitor. Por isso, vamos usar a seguinte versão alternativa de malloc: void *mallocc( size_t nbytes) { void *ptr; ptr = malloc( nbytes); if (ptr == NULL) { printf( "Socorro! malloc devolveu NULL!\n"); exit( EXIT_FAILURE); } return ptr; } O parâmetro de mallocc é do tipo size_t. Em muitos computadores, size_t é equivalente a unsigned int. A função mallocc não está em nenhuma biblioteca e é desconhecida fora destas notas de aula. Ela é apenas uma abreviatura conveniente. Função free As variáveis alocadas estaticamente dentro de uma função desaparecem quando a execução da função termina. Já as variáveis alocadas dinâmicamente continuam a existir mesmo depois que a execução da função termina. Se for necessário liberar a memória ocupada por essas variáveis, é preciso recorrer à função free. 2 A função free libera a porção de memória alocada por malloc. O comando free( ptr) avisa o sistema de que o bloco de bytes apontado por ptr está livre. A próxima chamada de malloc poderá tomar posse desses bytes. A função free não deve ser aplicada a uma parte de um bloco de bytes alocado por malloc; aplique free apenas ao bloco todo. Convém não deixar ponteiros "soltos" (= dangling pointers) no seu programa, pois isso pode ser explorado por hackers para atacar o seu computador. Portanto, depois de cada free( ptr), atribua NULL a ptr: free( ptr); ptr = NULL; Atribuir um valor a um ponteiro que se tornou inútil é decididamente deselegante, mas não há como lidar com hackers de maneira elegante… Para não cansar o leitor com detalhes repetitivos, estas notas não seguem a recomendação de segurança acima. Vetores e matrizes Eis como um vetor (= array) com n elementos inteiros pode ser alocado (e depois desalocado) durante a execução de um programa: int *v; int n, i; scanf( "%d", &n); v = mallocc( n * sizeof (int)); for (i = 0; i < n; ++i) scanf( "%d", &v[i]); for (i = n; i > 0; --i) printf( "%d ", v[i-1]); free( v); (A propósito, veja observação sobre vetores e endereços em outra página.) Do ponto de vista conceitual (mas apenas desse ponto de vista) o comando v = mallocc( 100 * sizeof (int)); tem o mesmo efeito que a alocação estática int v[100]; A propósito, convém lembrar que não permite escrever "int v[n]", a menos que n seja uma constante, definida por um #define. Matrizes bidimensionais são implementadas como vetores de vetores. Uma matriz com m linhas e n colunas é um vetor cada um de cujos m elementos é um vetor de n elementos. O seguinte fragmento de código faz a alocação dinâmica de uma tal matriz: 3 int int A = for **A; i; mallocc( m * sizeof (int *)); (i = 0; i < m; ++i) A[i] = mallocc( n * sizeof (int)); O elemento de A que está no cruzamento da linha i com a coluna j é denotado por A[i][j]. Exercícios 2. Escreva uma função que receba um caractere c e transforme c em uma cadeia de ,caracteres, ou seja, devolva uma cadeia de caracteres de comprimento 1 tendo c como único elemento. 3. Escreva um programa que leia um número inteiro positivo n seguido de n números inteiros e imprima esses n números em ordem invertida. Por exemplo, ao receber 5 222 333 444 555 666 o seu programa deve imprimir 666 555 444 333 222 O seu programa não deve impor limitações sobre o valor de n. 2. Tipos Estruturados / Registros / Structs Um registro é uma coleção de várias variáveis, possivelmente de tipos deferentes. Na linguagem C, registros são conhecidos como STRUCTS. O exemplo abaixo declara um registro data com três campos inteiros: struct { int dia; int mês; int ano; } data; É indicado que se coloque sempre o nome do registro, outra forma de representar: struct dma { int dia; int mês; int ano; }; struct data x; /* um registro x do tipo data */ Para atribuição de valores nos campos de um registro, temos: x.dia = 23; x.mes = 08; x.ano = 2013; struct data x; /* um registro x do tipo data */ 4 Exemplo: A função abaixo recebe a data de início de um evento e a duração do evento em dias. Ela devolve a data de fim do evento. struct dma fim_evento (struct dma datainicio, int duracao) { struct dma datafim; . . . . . . datafim.dia = ... datafim.mes = ... datafim.ano = ... return datafim; } O código foi omitido porque é um tanto enfadonho: deve levar em conta a existência de meses com 31 dias, de meses com 30 dias, com 29 dias etc. Eis como essa função poderia ser usada: int main( void) { struct dma a, b; int d; scanf( "%d %d %d", &a.dia, &a.mes, &a.ano); scanf( "%d", &d); b = fim_evento( a, d); printf( "%d %d %d\n", b.dia, b.mes, b.ano); return EXIT_SUCCESS; } Exercícios 1. Complete o código da função fim_evento acima. 2. Escreva uma função que receba dois structs do tipo dma, cada um representando uma data válida, e devolva o número de dias que decorreram entre as duas datas. 3. Escreva uma função que receba um número inteiro que representa um intervalo de tempo medido em minutos e devolva o correspondente número de horas e minutos (por exemplo, converte 131 minutos em 2 horas e 11 minutos). Use uma struct como a seguinte: struct hm { int horas; int minutos; }; Structs e ponteiros Cada registro tem um endereço na memória do computador. (Você pode imaginar que o endereço de um registro é o endereço de seu primeiro campo, mas essa detalhe é irrelevante.) É muito comum usar um ponteiro para guardar o endereço de um registro. Dizemos que um tal ponteiro aponta para o registro. Por exemplo, struct dma *p; struct dma x; p = &x; (*p).dia = 31; /* p é um ponteiro para registros dma */ /* agora p aponta para x */ /* mesmo efeito que x.dia = 31 */ 5 [Cuidado! A expressão *p.dia, que equivale a *(p.dia), tem significado muito diferente de (*p).dia.] A expressão p->mes é uma abreviatura muito útil para a expressão (*p).mes: p->mes = 8; p->ano = 1998; /* mesmo efeito que (*p).mes = 8 */ Registros podem ser tratados como um novo tipo-de-dados. Por exemplo, typedef struct dma data; data x; data *p; p = &x; TYPEDEF A sintaxe de typedef é fácil. Primeiro, escreva uma declaração de uma variável do tipo desejado. Por exemplo, int *ptri; declara uma variável ptri (do tipo ponteiro-para-inteiro). Agora escreva "typedef" antes da declaração: typedef int *ptri; Com isso, ptri passa a ser o nome de um novo tipo (idêntico ao tipo ponteiro-parainteiro). Esse tipo pode ser usado para declarar novos ponteiros-para-inteiros, como p e q: ptri p, q; Outro exemplo: struct { int x; int y; } ponto; declara uma variável ponto (que é um par ordenado de inteiros). Preceda a declaração de um typedef: typedef struct { int x; int y; } ponto; Agora ponto passa a ser o nome de um novo tipo (idêntico a par ordenado de inteiros). Esse tipo pode ser usado para declarar novos pontos: ponto a, b; Exercícios 4. Defina um registro empregado para guardar os dados (nome, sobrenome, data de nascimento, RG, data de admissão, salário) de um empregado de sua empresa. Defina um vetor de empregados para armazenar todos os empregados de sua empresa. Referências: • ASCENCIO, Ana F. G.; CAMPOS, Edilene A. V. de. Fundamentos da programação de computadores. 2ª ed. São Paulo: Pearson Pratice Hall, 2007. • MIZRAHI, Victorine V. Treinamento em Linguagem C. 2ª ed. São Paulo: Pearson Pratice Hall, 2008. 6