Olá e bem-vindo. Agora que entendemos a alocação de
memória dinâmica, temos que implementá-la, temos que realmente saber como fazê-lo em código c.
Então, vamos falar sobre três funções realmente
importantes, malloc, calloc, realloc.
Você vê que eles têm nomes em que a alocação
de memória é chamada e realocação.
A maneira mais simples e comum de alocar memória é
por meio da função de biblioteca padrão chamada malloc.
Isso está alocando memória dinâmica em tempo de execução.
Para chamar esta função, você precisa incluir o arquivo de cabeçalho lib padrão.
Você especifica o número de bytes de memória que deseja alocar como seu argumento.
O que é retornado dessa função é o endereço do primeiro byte de memória alocado.
Como você recebe um endereço retornado, um ponteiro é o único lugar para colocá-lo.
Portanto, é por isso que a alocação dinâmica de memória só é
possível por meio desse conceito de ponteiro, pois ele retorna endereços.
Você só pode colocar um endereço em um ponteiro, é isso que um ponteiro armazena, um ponteiro armazena um endereço.
Então, aqui está como você faz isso, sintaxe muito simples.
Normalmente, ao criar seu ponteiro, você não precisa.
Você pode criar seu ponteiro e defini-lo como nulo.
E então, na próxima linha, você pode malloc.
Mas aqui está a sintaxe padrão que você está criando seu ponteiro int,
ponteiro para um tipo int.
E então você perceberá que chama malloc.
E aqui, estamos dizendo malloc, vamos criar 100 bytes para malloc
e então vamos convertê-lo para um ponteiro int.
Como malloc retorna apenas um endereço, você deve convertê-lo em
um ponteiro final porque esse endereço está
armazenando um ponteiro int.
Esta é a maneira padrão de fazer isso.
Agora você está solicitando 100 bytes de
memória e atribuindo-os ao endereço do bloco de memória ao número p.
pNumber irá apontar para o primeiro local interno no início dos 100 bytes que foram alocados.
Isso significa que, como os inteiros têm 4 bytes por peça, eles
podem conter 25 valores inteiros.
E, novamente, isso supõe que você tenha 4 bytes.
Agora, dependendo do sistema que você está executando, eles podem ser maiores, talvez menores.
Então você deve ver o problema com isso. Este código não funcionará em
sistemas diferentes porque você está alocando com base em uma suposição, com
base na suposição do tamanho interno.
Portanto, não é muito bom usar malloc
dessa maneira com um número constante 100.
O que você deseja fazer é usar o operador sizeof.
Portanto, seria melhor remover a suposição de que os ints têm 4 bytes.
Em vez disso, se você deseja alocar espaço para 25 inteiros dentro do
argumento malloc, especifique o
número de inteiros que deseja alocar e multiplique-o pelo tamanho dos inteiros.
E isso tornará o código muito mais portátil.
Este código agora funcionará em diferentes sistemas, diferentes sistemas operacionais, diferentes arquiteturas.
Você ainda converte o valor de retorno do endereço porque ele deve estar apontando para
um tipo int, então você diz int star. A única diferença aqui é que você usa o operador sizeof.
O operador Sizeof é realmente importante quando você está alocando memória usando malloc
ou usando calloc ou realloc.
Você sempre usa e deseja usar o operador
sizeof para o tipo que está alocando e, em seguida, multiplica
isso por quantos números específicos deseja, portanto, se quiser armazenar
25 int, você deve fazer 25.
Portanto, o argumento de malloc acima está indicando claramente que bytes suficientes para acomodar
25 valores do tipo int devem ser disponibilizados.
Além disso, como mencionei, observe o elenco. Isso converte o endereço, retornado
pela função para o ponteiro de tipo para o ponteiro int e int.
Malloc retorna um ponteiro do tipo void, é o que o torna tão útil.
Ele retorna esse endereço, mas é um ponteiro vazio.
Lembre-se, falamos sobre ponteiros void e por que ponteiros void são
úteis porque agora você pode ter uma função que pode receber parâmetros diferentes e pode
retornar um ponteiro void. Agora você está vendo o
poder de retornar um ponteiro vazio,
novamente, mas para usá-lo, para realmente atribuir dados a ele, para desreferenciá-lo, você tem
que lançá-lo.
Se não quiséssemos lançar isso, poderíamos dizer void star p number.
Não o lançamos, mas teríamos que eventualmente lançá-lo, uma vez
que quiséssemos retirá-lo de referência.
Mas é por isso que temos que lançar de malloc. Ele está retornando um endereço e você deseja que ele aponte
para um tipo int, e está retornando um tipo nulo.
Você pode solicitar qualquer número de bytes.
No entanto, depende do seu sistema e de quanta memória você tem no sistema.
Portanto, se você tentar criar ou solicitar uma tonelada de bytes
ou houver alguma limitação, poderá falhar.
E quando ele falha, ele retorna um valor nulo, malloc retornará nulo.
E isso é bom porque os ponteiros quando apontam nulos, significa que eles não
estão apontando para nenhuma memória, então
você não pode corromper ou alterar nenhuma memória inadvertidamente.
Portanto, é sempre uma boa ideia, depois de
fazer um malloc, verificar imediatamente se há nulo
antes de usá-lo, assim como
quando você atribui dados ou quando você desreferencia um ponteiro.
Você quer ter certeza de que está realmente apontando para algo.
Então aqui, estamos alocando alguma memória para o número p nosso ponteiro e então
estamos dizendo se não o número p.
Lembre-se de que o nulo é zero.
Portanto, você pode usar um não nele. Você não precisa realmente compará-lo com o valor nulo.
Você pode, você pode compará-lo com null, torna-o mais legível, talvez torne-o mais explícito.
Mas você pode simplesmente dizer não número p, e isso realmente significa não
um, e nulo é zero. Portanto, isso verificará se há nulo.
E se for nulo, isso significa basicamente que a
memória não pôde ser alocada. Se você não consegue alocar memória em seu programa, você está com problemas.
E então o que você geralmente deve fazer neste código para lidar
com uma falha de alocação de memória é abortar, deve
chamar exit, deve sair de seu programa. Porque se seu programa não puder alocar memória, seu programa está bloqueado.
Então, normalmente, você chama um aborto acidental se não puder alocar memória.
Ao menos ao fazer isso, você pode exibir uma mensagem para
encerrar o programa. É muito melhor do
que permitir que o programa continue quando você não estava, não podemos alocar memória.
Porque então você pode continuar, você pode tentar usar essa memória,
ela será nula, então o programa irá travar de qualquer maneira, mas
você não necessariamente saberá o porquê.
Você tem que olhar para a pilha de chamadas. Levará muito mais tempo para depurar esse erro.
Liberando memória. Já mencionei que, como
você é o programador responsável por alocar a memória, precisa se
livrar da memória quando não estiver mais usando, porque a memória é um
recurso vital, especialmente em sistemas embarcados.
Você não deve usar memória se não precisar dela. Então você tem que liberar a memória.
Você sempre deseja liberar a memória quando não for mais necessária.
A memória que você alocar no heap será liberada automaticamente quando o programa existir, o que
é bom porque ele faz alguma limpeza.
Mas é muito melhor liberar explicitamente a memória quando terminar, mesmo que
seja um pouco antes de você sair.
É mais limpo,
sempre faça de graça ou sempre libere a memória depois de
alocar, você quer seguir essa regra. Esta é uma regra estrita e estrita.
Sempre libere memória se você alocá-la.
Um vazamento de memória ocorre quando você aloca parte da memória dinamicamente
e não retém a referência, portanto, não é possível liberá-la.
Talvez você esteja fora do escopo dessa referência, você não pode. Você não o libera.
Isso é chamado de vazamento de memória. Há memória alocada, mas não está mais sendo usada.
Isso geralmente ocorre quando você está em um loop e está alocando memória em um loop.
Você está por dentro. Você aloca essa memória. Você o usa dentro do
loop e, em seguida, salta para fora do loop e não o solta mais.
Cada iteração do loop o está alocando.
Portanto, para liberar memória que você alocou dinamicamente, você
ainda deve ter acesso a esse endereço.
Normalmente, você deseja uma memória livre quando terminar de usá-la e,
geralmente, no método que a alocou.
No entanto, se você está passando essa memória para outra função e
então é responsabilidade dessa função, mas geralmente você deseja liberar a memória e
a função que você alocou.
Para liberar a memória para um bloco de memória alocada
dinamicamente cujo endereço você armazenou em um ponteiro, tudo o que você precisa
fazer é chamar a função free.
Isso também está na biblioteca padrão. Basta passar o ponteiro para a função livre.
E geralmente, depois de passar esse ponteiro, você deseja defini-lo como nulo porque
isso significa que você não tem memória alocada para ele.
A função free tem um parâmetro formal do tipo void
star, então você pode passar qualquer tipo
de ponteiro e funcionará.
Você não precisa realmente lançá-lo ou algo parecido.
Desde que o número p contenha o endereço que foi retornado quando a memória foi alocada, todo
o bloco de memória estará livre para uso posterior. Outros programas podem usar essa memória, seu programa
agora pode usar essa memória, o que
é significativo quando você está em um sistema embarcado.
E então, como mencionei, você sempre deseja definir o ponteiro
como nulo para significar que esse ponteiro não está mais apontando para nada.
Essa memória foi liberada.
Existe outra função interessante chamada calloc.
Isso tem algumas vantagens em relação ao malloc. É muito semelhante ao malloc,
mas o que faz é alocar a memória como um número de elementos em um
determinado tamanho e também inicializar a memória.
Essa é a diferença, inicialização. Falei sobre a importância da inicialização.
É por isso
que você sempre deseja inicializar os dados para que você entenda que há
dados lá que você está esperando.
Quando você desinicializa os dados, pode haver dados neles dos quais você não tem ideia.
Portanto, sempre inicialize seus dados com algo.
E calloc é um pouco melhor porque faz isso por você.
Leva dois argumentos, tem dois parâmetros.
O número de itens de dados para os quais o espaço é necessário, o tamanho de cada item de dados.
É declarado no cabeçalho lib padrão.
Você chama assim como chama malloc. Tem praticamente a mesma aparência.
Neste exemplo, estamos criando um
ponteiro de inteiro e alocando 75 inteiros de
sizeof int. Portanto, são necessários dois parâmetros.
É um pouco mais limpo porque você não precisa realmente fazer uma multiplicação.
Em malloc, você tinha que fazer 75 eventos de tamanho de tempo.
Aqui você diz apenas 75, está
criando 75 ints e o tamanho de cada um é 4 bytes ou
o que quer que o sistema retorne do tamanho do operador.
Mas, novamente, a importância disso é que ele inicializará todos os dados também, o valor de retorno
pode ser nulo se não for capaz de alocar a memória que, por sua vez, você não deve abortar.
Mas é muito semelhante ao uso de malloc, exceto que a grande vantagem é esta inicialização.
Todos os ints são inicializados em zero, e isso é realmente um grande negócio,
uma grande vantagem.
Também é um pouco mais fácil de usar com os dois parâmetros.
Vamos falar sobre outra função chamada realloc.
A função realloc permite reutilizar ou estender a memória que você
alocou anteriormente usando malloc ou calloc.
Portanto, esta é uma maneira de alterar dinamicamente a quantidade de memória
alocada durante a execução.
Você pode inicialmente criar muita memória e então perceber que
não é memória suficiente. Bem, o que você pode fazer é simplesmente
chamar realloc, e isso realocará a memória.
Ele espera dois valores de argumento, um ponteiro contendo um endereço que
foi anteriormente transformado por uma chamada para malloc.
Portanto, você tem um ponteiro que tem alguma memória alocada ou calloc e o
tamanho e os bytes da nova memória que deseja alocar.
Portanto, isso alocará a quantidade de memória especificada pelo segundo argumento,
que é o segundo argumento. Ele transfere o conteúdo da memória
alocada anteriormente, referenciada pelo ponteiro, de modo que sabe automaticamente quanta memória
você alocou anteriormente porque você passou o ponteiro.
E então ele realmente adicionará a isso, a memória recém-alocada.
E retorna um ponteiro vazio para a nova memória.
Portanto, você deve reformulá-lo ao chamar realloc como os outros.
Ele retorna nulo se você não conseguiu realocar, o que
provavelmente é um erro fatal e você deve abortar.
O recurso mais importante dessa operação é que o
realloc preserva o conteúdo da área de memória original, você está apenas estendendo essa memória.
Ele pode estar fragmentado porque você pode não ter conseguido fazer isso em sequência,
mas ainda tem toda aquela memória que pode atribuir aos dados do now.
Vejamos um exemplo.
Neste exemplo, vamos usar malloc e, em seguida, vamos realocar a memória e, em
seguida, vamos liberá-la.
Então, aqui estamos criando um ponteiro de personagem.
Não estamos inicializando para null, provavelmente deveríamos.
Mas ele não tem nenhuma memória alocada para o que está apontando.
Possui memória alocada para armazenar o
ponteiro, que é apenas um endereço. Mas se você tentar ir para esse endereço, seu programa será o host.
Então o que você faz é chamar malloc.
Aqui estamos dizendo malloc 15.
Isso criará 15 bytes para um ponteiro de caractere.
Nós o lançamos, porque ele retorna um ponteiro vazio. Portanto, str é um ponteiro
agora que tem memória alocada para o que está apontando.
Podemos então usar str copy para passar alguns dados para o que está
apontando, uma string e
imprimimos a string no endereço.
Bem, então percebemos que o que alocamos 15 bytes não é
grande o suficiente.
Então, chamamos realloc, e passamos o
ponteiro que é str e queremos realocar mais 25 bytes e isso retorna
um ponto vazio, pois temos que lançá-lo novamente.
Agora ainda teremos Jason na memória.
Agora podemos chamar string concatenada e concatenar à string com, então
agora será Jason. com e depois imprimimos.
E por último, mas não menos importante, provavelmente
o mais importante neste slide, para que não haja vazamento de memória, chamamos de graça.
Ligue sempre de graça quando ligar para malloc.
Então, vamos dar uma olhada neste exemplo dentro dos blocos de código.
Aqui está nosso exemplo de código dentro de
nosso editor de blocos de código.
E a primeira coisa que você deve notar é
que estamos incluindo o arquivo de cabeçalho lib padrão porque é onde estão
nossas funções malloc e nossas funções realloc, bem como nossa função livre.
Além disso, porque estamos usando algumas funções str, concatenação e
cópia, estamos incluindo string. h.
Agora, na linha oito, vamos criar nosso ponteiro de personagem.
Não há memória alocada para ele.
Devemos inicializar isso como nulo porque ele
não está apontando para nada ainda. Na linha 11, chamamos malloc.
E provavelmente deveríamos dizer 15 vezes o tamanho
de char caso o tamanho dos bytes de
char seja diferente e então o lançamos em um
ponteiro de caractere
porque malloc retorna um void. Em seguida, copiamos.
Se tentássemos copiar isso e não fizéssemos nada na linha 11,
o programa travaria porque não temos memória alocada para os pontos de string.
Em seguida, apenas imprimimos a string e o endereço.
Agora chamamos realloc.
Precisamos armazenar mais dados do que 15
bytes, então vamos realocar com 25
bytes e provavelmente queremos fazer novamente sizeof char para
ser consistente.
Nós lançamos isso e então concatenamos em nosso local de
memória existente que contém jason. com, e vamos imprimir o jason. com.
E, por último, mas não menos importante, na linha 20, liberamos essa memória, o que
é muito importante.
Então, vamos prosseguir e executar isso. E o que você verá
é que, quando imprimimos pela segunda vez, temos a concatenação.
Portanto, nossa
primeira string, temos memória alocada e é um endereço 000C2918.
Em seguida, criamos um novo endereço realocando e tomando o tamanho existente do ponteiro que
passamos como um argumento.
Portanto, 000C0CD8 tem mais dados alocados, os
dados existentes em
jason e mais 25 bytes.
E então o que você nota é que a concatenação funciona.
Porque quando realocamos,
colocamos o conteúdo jason naquela memória e apenas o estendemos.
Então, muitas vantagens em usar esses malloc, essas chamadas calloc e realloc grátis.
Você quer usar isso, você quer entender isso. Funções muito simples de usar.
Então, em nosso último slide, vamos examinar algumas diretrizes.
Você deseja evitar a alocação de muitas pequenas quantidades de memória, isso não é eficiente.
Quando você aloca memória no
heap, há alguma sobrecarga com ele.
E se você alocar pequenos blocos de memória, terá muito mais sobrecarga do que
alocar menos blocos maiores.
Portanto, aloque a memória de que você
precisa, mas não faça isso a cada duas instruções com realocação, faça
grandes porções.
Guarde a memória apenas pelo tempo que precisar. A memória é um recurso escasso.
Quando está na pilha, é ainda mais escasso.
Então, quando você terminar com um bloco de memória, ligue gratuitamente.
Sempre certifique-se de fornecer memória para a liberação que você alocou, decida onde em seu
código você irá liberar a memória ao escrever o código que a aloca.
Certifique-se de ter acesso a esse ponteiro.
Se você precisa tornar o ponteiro global, você precisa fazer isso.
Mas geralmente, onde quer que você aloque nessa função, você deve liberá-lo nessa função.
Se você tiver outras funções que liberam
memória para você, está assumindo que isso não é bom. Há muitos acoplamentos e dependências aí.
A pessoa que o cria deve ser a pessoa que o exclui.
Não coloque a responsabilidade sobre outras funções e coisas assim.
Certifique-se de não sobrescrever inadvertidamente o endereço de memória que você alocou no
heap antes de liberá-lo, isso causará um vazamento de memória.
Portanto, se você tentar gravar muitos bytes nele ou se houver overflows, poderá
ter problemas.
Portanto, seja especialmente cuidadoso ao alocar memória em um loop,
pois isso pode causar problemas de estouro de buffer.
Eu espero que isso tenha sido útil. A alocação de memória dinâmica é um grande negócio.
Vou lhe dar um desafio que o ajudará a se familiarizar com
isso, mas certifique-se de entender esse conceito. Obrigada.