Ir para conteúdo

Alguns pontos interessantes sobre malloc()


Rick Santos

Posts Recomendados

Neste tópico pretendo realizar uma breve apresentação de diversos pontos que considero importantes relativamente à função malloc() em sistemas Linux.

malloc() tem como função alocar memória dinamicamente, criando ou alterando assim um segmento pertencente ao processo chamado Heap. (Alterando também pois durante a carregamento do programa para a memória, tanto o Loader do OS quando a própria libc podem (mas nem 100% das vezes) ter pré-alocado o segmento Heap com páginas suficientes para o processo em questão, para assim, evitar o mapeamento de novas)

A função malloc() é declarada em em "stdlib.h" a partir do seguinte protótipo: 

void *malloc(size_t size);

Isto quer dizer que malloc() alocará size quantidade de memória e retornará um ponteiro  * do tipo void para o primeiro endereço do segmento alocado. (A Heap não será inicializada com nenhuns dados inicialmente). 

Quando malloc() é executada com sucesso retorna um ponteiro do tipo void, isto quer dizer que (falando a nível de programação high-level), o ponteiro não tem nenhum tipo associado, sendo assim podendo ser usado por qualquer tipo de variável e ficando sempre com o tamanho de 8 bytes (64 bits), como qualquer ponteiro em arquiteturas atuais. Por outro lado, caso não tenha havido sucesso, ou seja não conseguindo alocar memória, quer dizer que o size = 0. Quando tal acontece malloc() retorna um valor NULL, em certos casos, poderá ser retornado um valor único de ponteiro podendo ser passado futuramente para uma outra função chamada free().

Quando a libc tenta alocar memória pedida por malloc() (atráves de system calls que irei descrever adiante) assume que existe espaço para a criação de novas páginas no espaço requisitado, o é quase sempre verdade devido ao uso do VAS (Virtual Address Space, derivado da Paginação). Mesmo assim é correto e essencial verificar sempre o valor de retorno, e caso não seja possível a alocação decidir o que fazer. Segue um código de exemplo sobre o falado até agora: 

#include <stdio.h>
#include <stdlib.h>     // malloc() tem protótipo aqui como: void *malloc(size_t size);
  
int main(int argc, char *argv[]) {
	void *ptr;       //Criação de um ponteiro do tipo void, podia ser de qualquer tipo.
  	
  	//Aqui usou-se a malloc para alocar sizeof(int) memória, neste caso será 4 bytes e caso malloc() retorne null, acontece algo, como coloquei em baixo.
  	if (!!(ptr = malloc(sizeof(int)) == NULL)) {     //OBS: O uso de !! foi apenas para o compilador não reclamar de uma atribuição "válida".
    	  printf("Não foi possível alocar memória\n");
          exit(EXIT_FAILURE); // OBS: EXIT_FAILURE é uma macro neste caso para 0, mas portável para todos os sistemas operativos, enquanto 0 em si não.
    }
  
    free(ptr); //Limpar o espaço alocado por malloc atráves de ptr. (Sempre necessário quando não se vai mais utilizar a memória alocada)
  	return 0;
}  

PS: NULL é definido em C, por vários headers - <stddef.h>, <locale.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>. A partir da seguinte forma:

#if !defined(NULL)
    #define NULL ((void*)0)
#endif

Querendo assim dizer que NULL é uma Macro, que pode ser atribuída a qualquer tipo de dado "void" e que aponta * para o valor 0, ou seja, usar NULL ou 0 é a mesma coisa pois NULL = 0.

--------------------------------------------------------------------------------------------------------------------------------------------------------

Algo importante é que o Kernel apenas aloca segmentos de memória (blocos de páginas) com tamanhos múltiplos do tamanho de uma página, que por padrão é 4 KiB (O Linux como outros sistemas podem trabalhar com páginas de tamanhos maiores habilitando a extensão PSE, mas não nos interessa por agora). Isto quer dizer que se o programa tentar alocar menos do que 4 KiB o Kernel continuará a alocar uma página pelo menos e o código só tera acesso ao espaço requisitado dessa página (O que equivale a um desperdício de memória, e assim de performance).

Felizmente a própria malloc() e outras funções de alocação, para ganho de performance, tem como objetivo reutilizar páginas já usadas e também aproveitar páginas já criadas mas com necessidade de remapeamento, assim evitando que elas andem "perdidas" pela memória.

---------------------------------------------------------------------------------------------------------

Na verdade a malloc() não é nenhuma função do "sistema", ela é apenas uma função usada em C que é definida em "stdlib.h". Na realidade malloc() utiliza duas System Calls para a alocação de memória, chamadas sbrk() e mmap().

A malloc() utiliza a system call sbrk() quando pretende alocar blocos de memória menores que MMAP_THRESHOLD (ou seja 128 KiB (32 páginas)), isto é feito a partir da expansão de memória já alocada para o processo a partir de um ponto chamado "program break". Program Break é o primeiro endereço linear logo após ao segmento BSS (inicializado a zeros) do processo, o program break ao ser aumentado a partir de void *sbrk(intptr_t increment) aumenta também o número de páginas disponíveis que foram requesitadas incialmente para uso da heap por malloc(). (PS: Chamar sbrk() com um incremento de zero vai dar origem à obtenção do endereço do Program Break do processo em questão).  Por fim o valor de retorno de sbrk() é um ponteiro para o endereço inicial do novo "segmento" de memória alocado. Caso tenha sido passado o argumento increment = 0 a sbrk(), o valor de retorno será o endereço do Program Break. Caso tenha havido algum erro o valor de retorno será do tipo void * com o valor de -1. (PS: O tipo do argumento passado para sbrk() varia entre sistemas POSIX, podendo ser: int, ssize_t, ptrdiff_t, intptr_t).

 

Para blocos maiores que  MMAP_THRESHOLD (ou seja 128 KiB (32 páginas)), malloc() utiliza a system call mmap(), que contém a seguinte definição:

void *mmap(void *addr, size_t lengthint " prot ", int " flags ,
          int fd, off_t offset);

Como podem ver é uma função longa, não vou falar detalhadamente dela mas sim cobri-la no geral como fiz com sbrk().

mmap() aloca novas páginas não acessíveis no user-space com tamanho passado como argumento "length" e a partir de um endereço inicial passado em "addr". Caso o argumento addr seja passado como NULL o Kernel tenta alocar memória a partir do endereço que ele achar mais "performático" alocar, caso contrário (seja passado algum endereço em addr), o Kernel tenta alocar memória a partir daquele endereço passado (A passagem do parâmetro addr é apenas uma "dica", agora não quer dizer que seja sempre cumprida). O argumento "prot" de mmap está relacionado com algumas "opções" de proteções das páginas criadas cujo a descrição está fora do escopo do tópico. Por fim, o valor de retorno de mmap() é o endereço da primeira posição do segmento de memória alocado por esta função.

PS: A função (system call) mmap() pode ser utilizada diretamente em C, assim tornando-se mais vantajo-so trabalhar com esta quando se quer ter um maior controle sobre páginas do que usar malloc(), que nos dará "recursos limitados". Basta incluir o seguinte header: #include <sys/mman.h>

----------------------------------------------------------------------------

Bem, esta foi apenas a apresentação de um pontos básicos que considero importantes acerca da tão conhecida função malloc() em C.

:D

Ricardo Santos

 


 
Link para o comentário
Compartilhar em outros sites

  • 2 semanas depois...

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...