Diferenças
Aqui você vê as diferenças entre duas revisões dessa página.
Ambos lados da revisão anterior Revisão anterior Próxima revisão | Revisão anterior | ||
c:alocacao_de_memoria [2023/09/15 14:39] – [Exercícios] maziero | c:alocacao_de_memoria [2024/10/10 18:27] (atual) – maziero | ||
---|---|---|---|
Linha 1: | Linha 1: | ||
+ | ====== Alocação de memória ====== | ||
+ | |||
+ | O espaço de endereços de um processo em execução é dividido em várias áreas distintas. As mais importantes são: | ||
+ | |||
+ | * //Text//: contém o código do programa e suas constantes. Esta área é alocada durante a chamada '' | ||
+ | * //Data// e //BSS//: são as áreas onde o processo armazena suas variáveis globais e estáticas. Têm tamanho fixo durante a execução do processo. | ||
+ | * //Stack//: contém a pilha de execução, onde são armazenadas os parâmetros, | ||
+ | * //Heap//: contém áreas de memória alocadas a pedido do processo, durante sua execução. Varia de tamanho durante a vida do processo. | ||
+ | |||
+ | {{ memoria-processo.png |}} | ||
+ | |||
+ | Um programa em C suporta três tipos de alocação de memória: | ||
+ | |||
+ | * A **alocação automática** ocorre quando são declaradas variáveis locais e parâmetros de funções. O espaço para a alocação dessas variáveis é reservado quando a função é invocada e liberado quando a função termina, automaticamente. Geralmente a alocação automática é feita na pilha (//stack//) de execução do programa. | ||
+ | * A **alocação estática** ocorre quando são declaradas variáveis globais ou estáticas; essa alocação geralmente usa a área //Data//. | ||
+ | * A **alocação dinâmica**, | ||
+ | |||
+ | ===== Alocação automática ===== | ||
+ | |||
+ | Por default, as variáveis definidas dentro de uma função (variáveis locais e parâmetros) são alocadas de forma automática na pilha de execução do programa (//stack//) a cada chamada da função, sendo descartadas quando a função encerra. Esse é o caso de '' | ||
+ | |||
+ | <code c soma.c> | ||
+ | #include < | ||
+ | |||
+ | // calcula a soma de 1 a n | ||
+ | int soma_n (int n) | ||
+ | { | ||
+ | int i, soma = 0 ; | ||
+ | |||
+ | for (i = 1 ; i <= n ; i++) | ||
+ | soma += i ; | ||
+ | |||
+ | return (soma) ; | ||
+ | } | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | printf ("Soma de 1 a 100 = %d\n", soma_n (100)) ; | ||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Se a função for chamada recursivamente, | ||
+ | |||
+ | <code c fatorial.c> | ||
+ | #include < | ||
+ | |||
+ | long int fatorial (int n) | ||
+ | { | ||
+ | long int parcial ; | ||
+ | |||
+ | printf (" | ||
+ | |||
+ | if (n < 2) | ||
+ | parcial = 1 ; | ||
+ | else | ||
+ | parcial = n * fatorial (n - 1) ; // observe a chamada recursiva | ||
+ | |||
+ | printf (" | ||
+ | |||
+ | return (parcial) ; | ||
+ | } | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | printf (" | ||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A execução gera o seguinte resultado: | ||
+ | |||
+ | antes: | ||
+ | antes: | ||
+ | antes: | ||
+ | antes: | ||
+ | antes: | ||
+ | antes: | ||
+ | depois: n: 1, parcial: 1 | ||
+ | depois: n: 2, parcial: 2 | ||
+ | depois: n: 3, parcial: 6 | ||
+ | depois: n: 4, parcial: 24 | ||
+ | depois: n: 5, parcial: 120 | ||
+ | depois: n: 6, parcial: 720 | ||
+ | Fatorial (6) = 720 | ||
+ | |||
+ | O padrão C99 permite a alocação automática de vetores de tamanho variável, ou seja, definidos em tempo de execução. O exemplo a seguir ilustra esse conceito (que deve ser usado com **muito cuidado**): | ||
+ | |||
+ | <code c> | ||
+ | |||
+ | int my_function (int n) | ||
+ | { | ||
+ | char name[n] ; // aloca string com tamanho " | ||
+ | |||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <note warning> | ||
+ | A pilha de execução do programa normalmente é pequena (8 MB ou menos). Por isso, a tentativa de alocar variáveis locais muito grandes pode resultar em erro de compilação ou de execução (SIGSEGV - // | ||
+ | </ | ||
+ | |||
+ | ===== Alocação estática ===== | ||
+ | |||
+ | A alocação estática ocorrem com variáveis globais (alocadas fora de funções). Uma variável alocada estaticamente nunca é descartada e mantém seu valor durante toda a vida do programa, exceto quando explicitamente modificada. Exemplo: | ||
+ | |||
+ | <code c varglobal-errado.c> | ||
+ | #include < | ||
+ | |||
+ | // variáveis globais, com alocação estática | ||
+ | int vetor[100] ; | ||
+ | int tam ; | ||
+ | |||
+ | void print_vetor () | ||
+ | { | ||
+ | for (int i = 0 ; i < tam ; i++) | ||
+ | printf ("%d ", vetor[i]) ; | ||
+ | } | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | tam = 10 ; | ||
+ | | ||
+ | print_vetor () ; | ||
+ | printf (" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <note warning> | ||
+ | O programa acima é um exemplo de como **NÃO SE DEVE PROGRAMAR**. Apesar de simplificar a escrita do código, usar variáveis globais diretamente dentro das funções torna o código menos claro e dificulta o reuso das mesmas. | ||
+ | </ | ||
+ | |||
+ | <code c varglobal-certo.c> | ||
+ | #include < | ||
+ | |||
+ | // variáveis globais, com alocação estática | ||
+ | int vetor[100] ; | ||
+ | int tam ; | ||
+ | |||
+ | void print_vetor (int v[], int n) | ||
+ | { | ||
+ | for (int i = 0 ; i < n ; i++) | ||
+ | printf ("%d ", v[i]) ; | ||
+ | } | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | tam = 10 ; | ||
+ | | ||
+ | print_vetor (vetor, tam) ; | ||
+ | printf (" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Uma **variável local** pode ser alocada estaticamente através do modificador '' | ||
+ | |||
+ | <code c estatica.c> | ||
+ | #include < | ||
+ | |||
+ | void acumula (int n) | ||
+ | { | ||
+ | | ||
+ | |||
+ | acum += n ; | ||
+ | | ||
+ | } | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | acumula (3) ; | ||
+ | acumula (5) ; | ||
+ | acumula (2) ; | ||
+ | |||
+ | return 0 ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | A execução desse código gera a seguinte saída: | ||
+ | |||
+ | acumulado: 3 | ||
+ | acumulado: 8 | ||
+ | acumulado: 10 | ||
+ | |||
+ | Variáveis com alocação estática são alocadas e inicializadas uma única vez durante a execução do programa, portanto seus valores se preservam entre chamadas consecutivas da função. | ||
+ | |||
+ | ===== Alocação dinâmica ===== | ||
+ | |||
+ | Na alocação dinâmica, o programa solicita explicitamente áreas de memória ao sistema operacional, | ||
+ | |||
+ | A alocação dinâmica permite criar variáveis com qualquer tamanho durante a execução do programa, sem precisar definir previamente um tamanho máximo para os dados no código-fonte. Além disso, torna possível a construção eficiente de estruturas de dados complexas, como árvores e grafos. | ||
+ | |||
+ | <note tip> | ||
+ | Por default, o compilador '' | ||
+ | </ | ||
+ | |||
+ | ==== Alocação simples ==== | ||
+ | |||
+ | Blocos de memória podem ser alocados dinamicamente através da chamada '' | ||
+ | |||
+ | <code c> | ||
+ | #include < | ||
+ | |||
+ | void *malloc (size_t size) | ||
+ | |||
+ | void free (void *ptr) | ||
+ | </ | ||
+ | |||
+ | A função '' | ||
+ | |||
+ | A função '' | ||
+ | |||
+ | Exemplo de uso: alocação dinâmica de um vetor de inteiros: | ||
+ | |||
+ | <code c aloca-vetor.c> | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | int *vetor ; | ||
+ | int tam ; | ||
+ | |||
+ | int main () | ||
+ | { | ||
+ | tam = 100 ; | ||
+ | |||
+ | // aloca um vetor para " | ||
+ | vetor = malloc (tam * sizeof (int)); | ||
+ | if (!vetor) | ||
+ | { | ||
+ | printf ("erro ao alocar o vetor\n" | ||
+ | exit (1) ; | ||
+ | } | ||
+ | |||
+ | // preenche o vetor | ||
+ | for (int i = 0; i < tam; i++) | ||
+ | vetor[i] = i ; | ||
+ | |||
+ | // imprime o vetor | ||
+ | for (int i = 0; i < tam; i++) | ||
+ | printf ("%d ", vetor[i]) ; | ||
+ | printf (" | ||
+ | |||
+ | // libera a memória do vetor | ||
+ | free (vetor) ; | ||
+ | vetor = NULL ; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <note important> | ||
+ | A memória alocada por um programa é automaticamente liberada quando sua execução encerra. Por isso, o uso da chamada '' | ||
+ | </ | ||
+ | |||
+ | ==== Redimensionamento de área alocada ==== | ||
+ | |||
+ | <code c> | ||
+ | #include < | ||
+ | |||
+ | void *realloc (void *ptr, size_t newsize) | ||
+ | </ | ||
+ | |||
+ | Esta função redimensiona o bloco previamente alocado apontado por '' | ||
+ | |||
+ | ==== Alocação de vetor ==== | ||
+ | |||
+ | <code c> | ||
+ | #include < | ||
+ | |||
+ | void *calloc (size_t count, size_t eltsize) | ||
+ | </ | ||
+ | |||
+ | Esta função aloca um bloco de memória de tamanho suficiente para conter um vetor com '' | ||
+ | |||
+ | Exemplo: | ||
+ | |||
+ | <code c> | ||
+ | float *v ; | ||
+ | int i ; | ||
+ | |||
+ | v = calloc (10000, sizeof (float)) ; | ||
+ | |||
+ | for (i = 0; i < 10000; i++) | ||
+ | v[i] = 1.0 / (i + 1) ; | ||
+ | </ | ||
+ | |||
+ | ==== Alocação semiautomática ==== | ||
+ | |||
+ | <code c> | ||
+ | #include < | ||
+ | |||
+ | void *alloca (size_t size) | ||
+ | </ | ||
+ | |||
+ | Esta função provê um mecanismo de alocação dinâmica semi-automática, | ||
+ | |||
+ | ===== Conteúdo inicial ===== | ||
+ | |||
+ | O conteúdo inicial de variáveis não-inicializadas depende da forma como são alocadas. A tabela a seguir resume as principais possibilidades: | ||
+ | |||
+ | ^ tipo de alocação ^ situação ^ conteúdo inicial ^ | ||
+ | | estática | variável global | zeros | | ||
+ | | estática | variável local estática | zeros | | ||
+ | | automática | variável local | indefinido (" | ||
+ | | dinâmica | ||
+ | | dinâmica | ||
+ | | dinâmica | ||
+ | | semiautomática | ||
+ | |||
+ | <note tip> | ||
+ | Obviamente, os " | ||
+ | </ | ||
+ | |||
+ | ===== Vazamento de memória ===== | ||
+ | |||
+ | Um [[https:// | ||
+ | |||
+ | Exemplo de vazamento de memória: | ||
+ | |||
+ | <code c> | ||
+ | ... | ||
+ | int *vetor ; | ||
+ | |||
+ | vetor = malloc (100 * sizeof (int)) ; // bloco 1 | ||
+ | if (!vetor) | ||
+ | abort () ; | ||
+ | |||
+ | ... | ||
+ | |||
+ | vetor = malloc (10 * sizeof (int)) ; // bloco 2 | ||
+ | if (!vetor) | ||
+ | abort () ; | ||
+ | |||
+ | ... | ||
+ | free (vetor) ; // libera o bloco 2 | ||
+ | ... | ||
+ | </ | ||
+ | |||
+ | Ao alocar o bloco 2, o programa altera o valor do ponteiro '' | ||
+ | |||
+ | <note important> | ||
+ | Vazamentos de memória podem levar o programa a esgotar a memória disponível e travar. Há ferramentas que permitem analisar vazamentos de memória em programas, como o [[https:// | ||
+ | </ | ||
+ | |||
+ | ===== Alocação segura ===== | ||
+ | |||
+ | Muitos programadores implementam funções de " | ||
+ | |||
+ | <code c> | ||
+ | // malloc seguro: testa se a alocação funcionou e limpa a área alocada. | ||
+ | // recebe como entrada o tamanho da área a alocar. | ||
+ | void *safe_malloc (unsigned long size) | ||
+ | { | ||
+ | void* ptr ; | ||
+ | |||
+ | // aloca uma área | ||
+ | ptr = malloc (size) ; | ||
+ | |||
+ | // testa a alocação | ||
+ | if (! ptr) | ||
+ | { | ||
+ | fprintf (stderr, " | ||
+ | exit (1) ; | ||
+ | } | ||
+ | |||
+ | // preenche a área com zeros (opcional) | ||
+ | memset (ptr, 0, size); | ||
+ | |||
+ | return (ptr) ; | ||
+ | } | ||
+ | |||
+ | // free seguro: libera a área alocada e zera o ponteiro, evitando double free. | ||
+ | // recebe como entrada o ENDEREÇO do ponteiro da área alocada. | ||
+ | void safe_free (void* ptr) | ||
+ | { | ||
+ | // para evitar o cast (void**) ao chamar safe_free (...) | ||
+ | void** ptr_aux = (void**) ptr ; | ||
+ | |||
+ | if (ptr_aux && *ptr_aux) | ||
+ | { | ||
+ | free (*ptr_aux) ; | ||
+ | *ptr_aux = NULL ; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // exemplo de uso: | ||
+ | int* vet ; | ||
+ | vet = safe_malloc (10000 * sizeof (int)) ; | ||
+ | ... | ||
+ | safe_free (&vet) ; | ||
+ | </ | ||
+ | |||
+ | ===== Exercícios ===== | ||
+ | |||
+ | - :-) Escreva um programa que aloque dinamicamente um vetor '' | ||
+ | - :-| Escreva um programa que aloque dinamicamente um vetor '' | ||
+ | - :-| Mude o programa anterior, escrevendo funções separadas para a) alocar o vetor e os inteiros ; b) preencher o vetor; c) imprimir o vetor; e d) liberar a alocação do vetor. | ||
+ | - :-/ Escreva um programa em C para a) criar uma lista encadeada simples com 1000 inteiros (com valores 1, 2, ..., 1000); b) percorrer a lista criada, imprimindo o valor contido em cada elemento. Cada elemento da lista encadeada é uma [[estruturas|estrutura]] com dois campos: um valor inteiro e um ponteiro para o próximo elemento. | ||
+ | - :-D Escreva um programa que aloque dinamicamente uma matriz '' | ||
+ | - :-D Mude o programa anterior, escrevendo funções separadas para a) alocar a matriz; b) preencher a matriz; e c) imprimir a matriz. | ||
+ | |||
+ | FIXME Mais exercícios | ||