Gerência de memória

O espaço de endereços de um processo é dividido em várias áreas distintas. As mais importantes são:

Um programa em C suporta três tipos de alocação de memória:

Alocação dinâmica de memória

#include <stdlib.h>
void * malloc (size_t size)

Esta função aloca uma nova região com size bytes de tamanho e retorna um pointeiro para o início da mesma (ou 0 em caso de erro). O conteúdo da nova área é indefinido. Eis um exemplo de uso:

struct mystruct *ptr;
...
ptr = (struct mystruct *) malloc (sizeof (struct mystruct));
if (ptr == 0) abort ();
#include <stdlib.h>
void free (void *ptr)

Esta função libera um bloco de memória previamente alocado, apontado por ptr.

#include <stdlib.h>
void * realloc (void *ptr, size_t newsize)

Esta função redimensiona o bloco previamente alocado apontado por ptr para o novo tamanho newsize. Retorna o novo endereço do bloco, que pode ser diferente do anterior, caso tenha sido necessário mudá-lo de lugar (o conteúdo original do bloco é preservado nesse caso ou em caso de erro).

#include <stdlib.h>
void * calloc (size_t count, size_t eltsize)

Esta função aloca um bloco de tamanho suficiente para conter um vetor com count elementos de tamanho eltsize cada um. O conteúdo do bloco alocado é preenchido por zeros.

#include <stdlib.h>
void * alloca (size_t size)

Esta função provê um mecanismo de alocação dinâmica semi-automática: o bloco alocado será liberado automaticamente ao encerrar a função onde o bloco foi alocado. O valor de retorno da chamada é o endereço de um bloco de tamanho size bytes, alocado na pilha da função atual (como se fosse uma variável local).

Na GLibC, os blocos alocados pelas funções acima sempre iniciam em um endereço múltiplo de 8 em plataformas de 32 bits. Caso seja necessário obter blocos iniciando em múltiplos de 16, 32, 64, etc, as funções a seguir estão disponíveis:

#include <malloc.h>
void * memalign (size_t boundary, size_t size)

Aloca um bloco de tamanho size cujo endereço inicial é um múltiplo de boundary (que deve ser 2n). Retorna o endereço do bloco alocado, que pode ser liberado mais tarde através da função free. Ver também a função posix_memalign.

Arquivos mapeados em memória

Sistemas operacionais modernos permitem mapear um arquivo em uma região de memória. Isso torna possível acessar o arquivo como se fosse um vetor de bytes em memória. Esse procedimento é mais eficiente que os tradicionais read/write, pois somente as regiões acessadas do arquivo são carregadas em memória. Como o mecanismo de mapeamento de arquivo faz uso dos mecanismos de memória virtual, é possivel mapear arquivos muito grandes na memória (o limite é o espaço de endereçamento).

#include <sys/mman.h>
void * mmap (void *address, size_t length,int protect, int flags, int filedes, off_t offset)

Cria um novo mapeamento em memória relacionado aos bytes offsetoffset+length-1 no arquivo aberto indicado pelo descritor filedes. O mapeamento não é removido ao fechar o descritor, ele deve ser desfeito explicitamente.

O campo address indica um endereço preferencial para mapear o arquivo na memória (ou NULL). O campo protect contém flags indicando o tipo de acesso permitido àquela região: leitura (PROT_READ), escrita (PROT_WRITE) ou execução (PROT_EXEC). Acessos inválidos resultarão em um sinal SIGSEGV. Essas proteções podem ou não ser suportadas pelo hardware.

O campo flags contém flags que controlam o tipo de mapeamento:

Deve-se observar que nem todos os descritores de arquivo podem ser mapeados em memória. Normalmente, somente arquivos normais e dispositivos orientados a blocos podem ser mapeados em memória. Além disso, essa funcionalidade não está disponível em sistemas mais antigos.

#include <sys/mman.h>
int munmap (void *addr, size_t length)

Remove mapeamentos efetuados entre (addr) e (addr + length), onde length deve ser o tamanho do mapeamento. Mais de um mapeamento pode ser removido em uma só operação.

#include <unistd.h>
#include <sys/mman.h>
int msync (void *address, size_t length, int flags)

Permite sincronizar um mapeamento de arquivo em memória com a imagem do arquivo em disco. A região é indicada pelos campos address e length. O campo flags pode conter as seguintes opções:

Proteção de páginas em memória

É possível informar ao sistema operacional que certas páginas de memória virtual nunca devem ser enviadas para o disco (swapping ou paging). Isso pode ser necessário em algumas circunstâncias:

Por causa de seus possíveis efeitos sobre outros processos (reduzindo a disponibilidade de memória do sistema), normalmente só processos do administrador podem travar páginas em memória (mais detalhes na página de manual de mlock).

#include <sys/mman.h>
int mlock (const void *addr, size_t len)

Permite travar na memória um conjunto de páginas do processo. A faixa de memória a travar inicia em addr e tem len bytes de tamanho. Todas as páginas atingidas por essa faixa são travadas.

#include <sys/mman.h>
int munlock (const void *addr, size_t len)

Faz o inverso de mlock.

#include <sys/mman.h>
int mlockall (int flags)

mlockall trava todas as páginas usadas pelo processo (ou que virão a ser usadas no futuro). flags indica o tipo de travamento a realizar: MCL_CURRENT (trava as páginas atuais) e/ou MCL_FUTURE (trava as páginas que vierem a fazer parte do espaço de endereços do processo no futuro).

#include <sys/mman.h>
int munlockall (void)

Faz o contrário de mlockall, destravando todas as páginas presentes e futuras do processo.

#include <sys/mman.h>
int mprotect(const void *addr, size_t len, int prot);

Controla a forma como uma região de memória pode ser acessada. Caso a forma de acesso definida seja violada, o processo recebe um sinal SIGSEGV. A região de memória a proteger inicia em addr e possui len bytes de tamanho (addr deve ser um múltiplo de PAGESIZE, pois a unidade básica de proteção é a página); o campo prot indica o tipo de proteção a aplicar, que pode ser uma combinação (OU binário) das seguintes macros:

Operações em blocos de memória

As funções aqui indicadas são úteis para operar com blocos de memória alocados estatica ou dinamicamente.

#include <string.h>
void * memcpy (void *restrict to, const void *restrict from, size_t size)

Copia size bytes do bloco iniciando no endereço from para o bloco iniciando no endereço to. O comportamento dessa função é imprevisível caso haja sobreposição das áreas de memória indicadas.

#include <string.h>
void * memmove (void *to, const void *from, size_t size)

Copia size bytes do bloco iniciando no endereço from para o bloco iniciando no endereço to, mesmo que haja sobreposição entre os blocos.

#include <string.h>
void * memccpy (void *restrict to, const void *restrict from, int test, size_t size)

Copia até size bytes do bloco iniciando em from para o bloco iniciando em to, parando ao copiar todos os bytes ou ao encontrar um byte cujo valor seja igual a test.

#include <string.h>
void * memset (void *addr, int value, size_t size)

Copia o valor de value (convertido para unsigned char) nos primeiros size bytes do bloco de memória iniciando em addr.

Depurando problemas de memória

As operações envolvendo ponteiros e alocação dinâmica de memória costumam levar a bugs complexos e muitas vezes difíceis de resolver. Existem várias ferramentas para auxiliar o programador a localizar e resolver problemas relacionados à alocação dinâmica de memória e uso inadequado de ponteiros em C/C++:

Atividades

A definir…