O gerenciamento de memória dinâmica em C permite alocar e liberar memória durante a execução do programa, oferecendo maior flexibilidade em comparação com a alocação estática.
- Necessidade de Alocação Dinâmica
As formas tradicionais de alocação de memória em C incluem:
int valor = 20; // Aloca 4 bytes na pilha (stack)
char array[10] = {0}; // Aloca 10 bytes contíguos na pilha (stack)
Esses métodos possuem limitações:
- O tamanho do espaço alocado é fixo.
- O tamanho de um array deve ser definido em tempo de compilação e não pode ser alterado após a alocação.
Em muitas situações, o tamanho exato da memória necessária só é conhecido durante a execução do programa. Para atender a essa demanda, a linguagem C introduziu a alocação dinâmica de memória, permitindo que os programadores gerenciem a alocação e a liberação de espaço de forma mais flexível.
- Funções
mallocefree
2.1 malloc
A função malloc (memory allocation) é utilizada para alocar um bloco de memória dinamicamente.
void* malloc (size_t size);
- Se a alocação for bem-sucedida,
mallocretorna um ponteiro para o início do bloco de memória alocado. - Caso contrário, retorna um ponteiro
NULL. É crucial verificar o valor de retorno demalloc. - O tipo de retorno é
void*, indicando quemallocnão determina o tipo dos dados a serem armazenados; o programador é responsável por fazer o type cast apropriado. - Se o parâmetro
sizefor 0, o comportamento demallocé indefinido pelo padrão C.
Observação: A unidade para o parâmetro size é bytes. A função retorna o endereço inicial do espaço alocado em caso de sucesso e NULL em caso de falha. O cabeçalho necessário é <stdlib.h>.
2.2 Diferenças entre Memória Alocada por malloc e Arrays
- A quanitdade de memória alocada por
mallocpode ser redimensionada, ao contrário dos arrays, cujo tamanho é fixo após a alocação. - A memória alocada por
mallocreside na heap, enquanto arrays declarados em tempo de compilação são alocados na stack.
2.3 free
A função free é utilizada para liberar a memória previamente alocada dinamicamente.
void free (void* ptr);
- Se o ponteiro
ptrnão aponta para um bloco de memória alocado dinamicamente, o comportamento defreeé indefinido. - Se
ptrforNULL, a funçãofreenão faz nada.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = NULL;
ptr = (int*)malloc(40); // Aloca espaço para 10 inteiros
if (ptr != NULL) {
for (int i = 0; i < 10; i++) {
*(ptr + i) = i * 2; // Preenche com valores
printf("%d ", *(ptr + i));
}
printf("\n");
} else {
fprintf(stderr, "Falha na alocação de memória.\n");
}
free(ptr); // Libera a memória alocada
ptr = NULL; // Evita ponteiro pendente (dangling pointer)
return 0;
}
Observações:
-
Após chamar
free(p), o ponteiropse torna um ponteiro pendente (dangling pointer). Embora a memória não pertença mais ao programa, o ponteiro ainda aponta para aquele endereço. Se a memória não for liberada explicitamente, o sistema operacional a recuperará quando o programa terminar. -
É uma boa prática usar
mallocefreeem pares para garantir que a memória seja corretamente gerenciada. -
Funções
callocerealloc
3.1 calloc
A função calloc (contiguous allocation) também é usada para alocação dinâmica de memória.
void* calloc (size_t num, size_t size);
- Esta função aloca memória para
numeelmentos, cada um com tamanhosizebytes. - A principle diferença em relação a
mallocé quecallocinicializa todos os bytes do bloco de memória alocado com zero antes de retornar o ponteiro.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = NULL;
// Aloca espaço para 10 inteiros e inicializa com 0
ptr = (int*)calloc(10, sizeof(int));
if (ptr == NULL) {
perror("calloc"); // Exibe a mensagem de erro do sistema
return 1;
} else {
// Verifica se a alocação foi bem-sucedida (boa prática)
for (int i = 0; i < 10; i++) {
printf("%d ", *(ptr + i)); // Imprime os valores (inicializados com 0)
}
printf("\n");
}
free(ptr);
ptr = NULL;
return 0;
}
Se for necessário que a memória alocada seja inicializada com zero, a função calloc oferece uma maneira conveniente de fazer isso.
3.2 realloc
A função realloc (re-allocation) adiciona flexibilidade ao gerenciamento de memória dinâmica, permitindo redimensionar blocos de memória previamente alocados.
void* realloc (void* ptr, size_t size);
ptré o ponteiro para o bloco de memória a ser redimensionado.sizeé o novo tamanho desejado para o bloco de memória.- A função retorna um ponteiro para o bloco de memória redimensionado.
realloctenta redimensionar o bloco original. Se possível, mantém os dados existentes e anexa ou remove espaço conforme necessário. Caso contrário, aloca um novo bloco, copia os dados do bloco original para o novo e libera o bloco original.
Existem duas situações principais ao redimensionar a memória:
- Situação 1: Há espaço contíguo suficiente após o bloco original. Nesse caso, o bloco original é expandido.
- Situação 2: Não há espaço contíguo suficiente.
reallocaloca um novo bloco maior, copia os dados do bloco antigo para o novo e libera o bloco antigo.
É crucial verificar o retorno de realloc, pois ele pode falhar e retornar NULL.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = NULL;
ptr = (int*)malloc(40); // Aloca 40 bytes (suficiente para 10 inteiros)
if (ptr == NULL) {
perror("malloc");
return 1;
} else {
// Inicializa os primeiros 10 inteiros
for (int i = 0; i < 10; i++) {
*(ptr + i) = i + 1;
}
}
// Tenta redimensionar para 80 bytes (suficiente para 20 inteiros)
int* new_ptr = (int*)realloc(ptr, 80);
if (new_ptr != NULL) {
ptr = new_ptr; // Atualiza o ponteiro se a realocação for bem-sucedida
} else {
perror("realloc");
// Se realloc falhar, ptr ainda aponta para a memória original de 40 bytes
// É importante liberar essa memória antes de sair ou continuar
free(ptr);
return 1;
}
// Inicializa os novos 10 inteiros
for (int i = 10; i < 20; i++) {
*(ptr + i) = i + 1;
}
// Imprime todos os 20 inteiros
for (int i = 0; i < 20; i++) {
printf("%d ", *(ptr + i));
}
printf("\n");
// Libera a memória
free(ptr);
ptr = NULL;
return 0;
}
Recomendação: Sempre utilize uma variável auxiliar para o retorno de realloc. Se a realocação falhar, o ponteiro original permanece válido e a memória que ele aponta ainda precisa ser liberada. Se você atribuir diretamente o retorno de realloc ao ponteiro original e a realocação falhar, você perderá o ponteiro para a memória original, resultando em um vazamento de memória.
3.3 Observações sobre realloc
Em certas condições, realloc pode ser usado para alocar memória, de forma semelhante a malloc:
// Equivalente a malloc(20)
void* memory = realloc(NULL, 20);
Neste caso, se ptr for NULL, realloc se comporta como malloc.
- Erros Comuns em Gerenciamento de Memória Dinâmica
Atenção: Os exemplos de código a seguir contêm erros e não devem ser imitados.
4.1 Desreferenciamento de Ponteiro Nulo
Tentar acessar a memória através de um ponteiro nulo causa a falha do programa.
void test_null_dereference() {
int* p = (int*)malloc(sizeof(int));
if (p == NULL) {
fprintf(stderr, "Falha na alocação.\n");
return; // Ou exit()
}
// Se a alocação falhar e p for NULL, a próxima linha causará crash
*p = 20;
free(p);
}
É essencial verificar se a alocação de memória foi bem-sucedida antes de usá-la.
4.2 Acesso Fora dos Limites da Memória Alocada
Acessar posições de memória além do bloco alocado pode corromper dados ou causar falhas.
void test_out_of_bounds() {
int* p = (int*)malloc(10 * sizeof(int)); // Aloca espaço para 10 inteiros
if (p == NULL) {
exit(EXIT_FAILURE);
}
for (int i = 0; i <= 10; i++) { // O loop vai até i=10, que está fora do limite
*(p + i) = i; // Acesso a *(p + 10) é um acesso fora dos limites
}
free(p);
}
4.3 Liberar Memória Não Alocada Dinamicamente com free
Chamar free em um ponteiro que não aponta para memória alocada dinamicamente (e não liberada) resulta em comportamento indefinido.
void test_free_non_dynamic() {
int a = 10;
int* p = &a; // p aponta para uma variável na stack
// free(p); // ERRO: Liberar memória da stack com free é incorreto
}
4.4 Liberar Apenas Parte de um Bloco de Memória Alocado
A função free deve ser chamada com o ponteiro que aponta para o *início* do bloco alocado.
int main() {
int* p = (int*)malloc(40);
if (p == NULL) return 1;
int* temp_ptr = p; // Guarda o ponteiro original
for (int i = 0; i < 5; i++) {
// *(temp_ptr + i) = i; // Exemplo de uso
temp_ptr++; // Avança o ponteiro
}
// free(temp_ptr); // ERRO: temp_ptr agora aponta para o meio do bloco alocado
free(p); // Correto: liberar usando o ponteiro original
p = NULL;
return 0;
}
Para evitar esse erro, utilize um ponteiro auxiliar para percorrer a memória alocada, mantendo sempre o ponteiro original (que aponta para o início do bloco) para a chamada de free.
4.5 Liberar o Mesmo Bloco de Memória Múltiplas Vezes
Liberar um bloco de memória que já foi liberado causa comportamento indefinido e geralmente leva à falha do programa.
void test_double_free() {
int* p = (int*)malloc(100);
if (p == NULL) return;
free(p);
// free(p); // ERRO: Liberar a mesma memória duas vezes
}
4.6 Esquecer de Liberar Memória Alocada (Vazamento de Memória)
Se a memória alocada dinamicamente não for liberada após o uso, ela permanece alocada até o fim do programa, o que é conhecido como vazamento de memória (memory leak).
void leaky_function() {
int* p = (int*)malloc(100);
if (p != NULL) {
*p = 20;
// A memória alocada por malloc(100) não é liberada aqui
}
}
int main() {
leaky_function();
// O programa continua, mas a memória alocada em leaky_function é perdida
while (1);
return 0;
}
É fundamental liberar toda a memória alocada dinamicamente que não é mais necessária.
- Arrays Flexíveis (Estruturas com Array de Tamanho Zero)
Uma estrutura em C pode conter um membro de array de tamanho zero, conhecido como array flexível. Isso permite que a estrutura seja alocada dinamicamente com um tamanho variável.
struct ExemploFlexivel {
int id;
int dados[0]; // Array flexível
};
Alguns compiladores podem gerar um aviso para int dados\[0\]. Nesses casos, pode-se usar int dados\[\].
struct ExemploFlexivelAlternativo {
int id;
int dados[]; // Array flexível
};
5.1 Características de Arrays Flexíveis
- O membro do array flexível deve ser o último membro da estrutura.
- A função
sizeofretornará o tamanho da estrutura sem incluir a memória do array flexível. - Ao alocar memória para uma estrutura com array flexível usando
malloc, o tamanho alocado deve ser maior que o tamanho da estrutura para acomodar os elementos do array flexível.
5.2 Uso de Arrays Flexíveis
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
int elementos[]; // Array flexível
} EstruturaComArrayFlexivel;
int main() {
int num_elementos = 100;
// Aloca memória para a estrutura + 100 inteiros
EstruturaComArrayFlexivel* ptr = (EstruturaComArrayFlexivel*)malloc(sizeof(EstruturaComArrayFlexivel) + num_elementos * sizeof(int));
if (ptr == NULL) {
perror("malloc");
return 1;
}
// Inicializa os membros da estrutura
ptr->id = 100;
for (int i = 0; i < num_elementos; i++) {
ptr->elementos[i] = i; // Acessa o array flexível
}
// Usa os dados...
free(ptr); // Libera toda a memória alocada (estrutura + array flexível)
return 0;
}
5.3 Vantagens de Arrays Flexíveis
-
Facilidade na Liberação de Memória: Ao alocar a estrutura e o array flexível como um único bloco, uma única chamada a
freelibera toda a memória associada. Isso simplifica o gerenciamento para o usuário da estrutura. -
Melhor Desempenho de Acesso: Memória contígua pode levar a um acesso mais rápido aos dados e reduzir a fragmentação da memória.
-
Áreas de Alocação de Memória em Programas C/C++
Os programas C/C++ utilizam diferentes áreas de memória:
- Stack (Pilha): Usada para variáveis locais, parâmetros de funções e endereços de retorno. A alocação e desalocação são automáticas e eficientes, mas o espaço é limitado.
- Heap (Monte): Usado para alocação dinâmica de memória (
malloc,calloc,realloc). A alocação e liberação são gerenciadas pelo programador. - Static/Data Segment (Segmento Estático/Dados): Usado para variáveis globais, variáveis
statice literais de string. A memória é alocada em tempo de compilação e liberada pelo sistema operacional ao final do programa. - Code Segment (Segmento de Código): Contém o código binário executável do programa.