Rick Santos Posted November 12, 2017 Posted November 12, 2017 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. Ricardo Santos
JuniorTCP Posted November 27, 2017 Posted November 27, 2017 Muito bom o material, usei muito o malloc() em estrutura de dados passando uma struct como base para o tamanho da memoria, Ótimo material parabéns, fez eu me recordar de um tempo muito bom em minha aprendizagem obrigado.
Recommended Posts
Archived
This topic is now archived and is closed to further replies.