Modelo de memória
Esta aula aborda o modelo de memória em C, explicando a diferença entre stack e heap, os segmentos de código, dados e BSS, e como endereços de memória são organizados. O aluno aprenderá a estrutura básica da memória de um programa C, fundamental para entender alocação dinâmica e gerenciamento de recursos.
Nesta aula, vamos mergulhar no modelo de memória dos programas escritos em C. Entender como a memória é organizada é crucial para escrever código eficiente, evitar vazamentos e compreender conceitos como alocação dinâmica, ponteiros e escopo de variáveis. A memória de um processo em C é dividida em regiões lógicas, cada uma com um propósito específico.
Vamos explorar as principais áreas: a pilha (stack), o monte (heap), e os segmentos estáticos (código, dados e BSS). Além disso, veremos como endereços de memória são representados e acessados. Ao final, você terá uma visão clara de onde suas variáveis e funções residem durante a execução.
Stack vs heap
A stack (pilha) é uma região de memória usada para armazenar variáveis locais, parâmetros de funções e endereços de retorno. Ela cresce para baixo (endereços decrescentes) e é gerenciada automaticamente pelo compilador: cada chamada de função aloca um novo quadro (frame) na pilha, que é liberado quando a função retorna. A stack é rápida e limitada em tamanho (tipicamente alguns MB), e o estouro dela causa o famoso stack overflow.
O heap (monte) é uma região de memória de alocação dinâmica, controlada pelo programador através de funções como malloc, calloc, realloc e free. O heap cresce para cima (endereços crescentes) e é mais lento que a stack, mas oferece flexibilidade para alocar memória em tempo de execução com tamanho variável. A memória alocada no heap persiste até ser explicitamente liberada, ou até o término do programa. Vazamentos de memória ocorrem quando o programador esquece de chamar free.
#include <stdlib.h>
void funcao() {
int a = 10; // alocado na stack
int *p = malloc(sizeof(int)); // alocado no heap
*p = 20;
free(p); // libera heap
}
int main() {
funcao();
return 0;
}Segmentos (código, dados, BSS)
Um programa compilado em C é organizado em segmentos na memória. O segmento de código (text) contém as instruções executáveis do programa. Geralmente é somente leitura para evitar modificações acidentais. O segmento de dados (data) armazena variáveis globais e estáticas inicializadas com valores diferentes de zero. O segmento BSS (Block Started by Symbol) contém variáveis globais e estáticas não inicializadas (ou inicializadas com zero). Essas variáveis são preenchidas com zeros antes da execução.
Além disso, há o segmento de constantes (rodata) que guarda literais como strings constantes. A organização típica de memória (endereços crescentes) é: segmento de código, segmento de dados, segmento BSS, heap (crescendo para cima), e stack (crescendo para baixo). Isso ajuda a evitar colisões entre heap e stack.
int global_inicializado = 42; // segmento de dados
int global_nao_inicializado; // segmento BSS
static int estatica_inicializada = 10; // dados
static int estatica_nao_inicializada; // BSS
int main() {
static int local_estatica = 5; // dados (inicializada uma vez)
return 0;
}Endereços
Endereços de memória são números que identificam localizações na memória RAM. Em C, você pode obter o endereço de uma variável usando o operador &. A impressão de endereços geralmente é feita com %p no printf. Os endereços são tipicamente hexadecimais e variam conforme a arquitetura e o sistema operacional.
Observação importante: endereços de variáveis na stack tendem a ser altos (próximos ao topo da memória virtual), enquanto endereços no heap são mais baixos. Segmentos de código e dados têm endereços fixos determinados pelo linker. A seguir, um exemplo que mostra endereços de diferentes regiões:
#include <stdio.h>
#include <stdlib.h>
int global_data = 100;
int global_bss;
int main() {
int local_stack = 1;
int *heap_ptr = malloc(sizeof(int));
static int static_data = 200;
static int static_bss;
printf("Codigo (funcao main): %p\n", main);
printf("Dados globais: %p\n", &global_data);
printf("BSS global: %p\n", &global_bss);
printf("Stack local: %p\n", &local_stack);
printf("Heap: %p\n", heap_ptr);
printf("Dados estaticos: %p\n", &static_data);
printf("BSS estatico: %p\n", &static_bss);
free(heap_ptr);
return 0;
}Visão geral
A organização da memória de um programa C pode ser visualizada como um diagrama de segmentos. Do endereço mais baixo para o mais alto: segmento de código (text), segmento de dados inicializados (data), segmento BSS, heap (crescendo para cima), e stack (crescendo para baixo). O heap e a stack podem crescer um em direção ao outro, mas o sistema operacional impede que se encontrem.
Entender essa estrutura ajuda a depurar problemas como estouro de pilha, vazamentos de memória e corrupção de dados. Além disso, explica porque variáveis locais não persistem após a função retornar (são destruídas com o frame da stack), enquanto variáveis alocadas com malloc permanecem até free.
// Representação conceitual (não executável)
// Endereços baixos
// +------------------+
// | Código (text) |
// +------------------+
// | Dados (data) |
// +------------------+
// | BSS |
// +------------------+
// | Heap |
// | (cresce p/ cima)|
// +------------------+
// | ... |
// +------------------+
// | Stack |
// | (cresce p/ baixo)|
// +------------------+
// Endereços altosBoas práticas
Sempre inicialize variáveis globais e estáticas explicitamente para evitar comportamentos indefinidos (embora o BSS garanta zero, é boa prática). Use free para toda memória alocada com malloc e evite acessar memória já liberada. Prefira alocação na stack quando possível, por ser mais rápida e segura. Para alocações grandes ou de tamanho dinâmico, use o heap.
Referências
- cppreference: Storage duration
- GNU C Library: Memory Allocation
- Learn-C: Memory Management
- TutorialsPoint: C Memory Management
- IBM: Memory layout of a C program
Exercícios
Explique a diferença entre stack e heap em termos de gerenciamento e velocidade.
✓ Resposta: A stack é gerenciada automaticamente pelo compilador, é rápida e de tamanho limitado. O heap é gerenciado manualmente pelo programador (malloc/free), é mais lento e permite alocação dinâmica de tamanho variável.O que acontece se você tentar alocar uma variável local muito grande na stack?
✓ Resposta: Ocorre um estouro de pilha (stack overflow), que geralmente causa um erro de segmentação (segfault) ou terminação do programa.Em qual segmento da memória são armazenadas variáveis globais não inicializadas?
✓ Resposta: No segmento BSS (Block Started by Symbol).Escreva um programa que imprima o endereço de uma variável local, uma global e uma alocada no heap.
✓ Resposta:#include <stdio.h> #include <stdlib.h> int global = 0; int main() { int local = 0; int *heap = malloc(sizeof(int)); printf("Local: %p\n", &local); printf("Global: %p\n", &global); printf("Heap: %p\n", heap); free(heap); return 0; }Por que o segmento de código é geralmente somente leitura?
✓ Resposta: Para evitar que o programa modifique suas próprias instruções acidentalmente ou maliciosamente, garantindo segurança e estabilidade.