Executando verificação de segurança...
6

Muito bom os dois posts! Mas gostaria de corrigir um único ponto na resposta do maniero, onde ele fala sobre objetos não mudarem de tamanho na heap:

Para alocar no heap, que costuma na maioria das linguagens, mas não necessariamente precisa ser compartilhado pelas threads, os objetos precisam ter tamanho conhecido no momento da alocação e não podem variar (sendo pedante). Seria fisicamente muito complicado dar variabilidade no tamanho do objeto. Pense em um caminhão onde os pacotes dentro dele podem mudar de tamanho, para onde vão os pacotes que estão do lado dele? É uma questão bem concreta. Por isso não pode encher até a boca caminhão tanque. E assim os motoristas roubam(vam) combustível, tirando uma parte e deixando o caminhão no sol para dar o mesmo volume (por isso hoje se mede a temperatura quando vai descarregar). Objetos na memória não são líquidos.

Na realidade, em C, é possível alterar o tamanho de objetos alocados na heap em tempo de execução com o comando realloc().

void *realloc(void *ptr, size_t new_size);

O realloc recebe o endereço do objeto a ser redimensionado e o novo tamanho pra esse objeto. Caso consiga alterar o tamanho do objeto, ele retornará o mesmo endereço de memória, caso contrário, ele executará os passos a seguir:

  1. Alocará um novo espaço de memória para o objeto;
  2. Copiará o conteúdo atual do objeto para o novo espaço de memória;
  3. Desalocará o espaço de memória anterior; e
  4. Retornará o endereço do novo espaço de memória.

É bem mais fácil visualizar seu funcionamento com arrays, mas em C, essa operação pode ser feita com qualquer tipo de dados. Por exemplo, se alocarmos um array de int de 10 posições (40 bytes) e depois quisermos reduzir seu tamanho para 5 posições (20 bytes), é bem provável (uma "quase-certeza") que o endereço de memória retornado seja o mesmo, o array só ficou menor e deixou um espaço livre de 5 posições (20 bytes) ao seu lado.

Exemplo 1:

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *array = malloc(10 * sizeof(int));
  printf("endereço anterior: %p\n", array);
  array = realloc(array, 5 * sizeof(int));
  printf("novo endereço:     %p\n", array);
  return 0;
}

Output do Exemplo 1:

endereço anterior: 0x1ccf2a0
novo endereço:     0x1ccf2a0

Obs: os endereços mudam em cada execução

Mas e se quisermos aumentar o tamanho do nosso array? Bom, às vezes ele vai alocar um novo espaço de memória, realizando toda a etapa de cópia do objeto pro novo espaço, como dito anteriormente, quando não houver espaço disponível ao seu redor. No exemplo a seguir, alocamos um array de 5 posições e depois tentamos redimensioná-lo para 10 posições, como "não existe" espaço livre ao seu redor, será retornado um novo endereço de memória.

Obs: usei "não existe" entre aspas porque existem detalhes sobre como o SO aloca esses espaços e como eles são expostos para o processo que não tenho conhecimento aprofundado pra explicar aqui

Exemplo 2:

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *array = malloc(5 * sizeof(int));
  printf("endereço anterior: %p\n", array);
  array = realloc(array, 10 * sizeof(int));
  printf("novo endereço:     %p\n", array);
  return 0;
}

Output do Exemplo 2:

endereço anterior: 0x229c2a0
novo endereço:     0x229c6d0

Mas ele também pode reaproveitar espaços liberados por objetos que estavam na heap mas foram desalocados. Nesse caso, ele vai simplesmente alterar o tamanho do objeto e retornar o mesmo endereço. No próximo exemplo, são alocados dois arrays de 10 posições, depois o primeiro array é desalocado e, quando o segundo é redimensionado para 20 posições, o realloc() reaproveitará o espaço livre deixado pelo array desalocado.

Exemplo 3:

#include <stdio.h>
#include <stdlib.h>

int main() {
  puts("Alocação inicial");
  int *array1 = malloc(10 * sizeof(int));
  int *array2 = malloc(10 * sizeof(int));

  printf("array1: %p\n", array1);
  printf("array2: %p\n", array2);
  puts("");

  puts("Removendo array1 e realocando array2");

  free(array1);  // liberando o array1
  array1 = NULL; // atribuindo o ponteiro nulo pra ficar mais bonito no print :D
  array2 = realloc(array2, 20 * sizeof(int));

  printf("array1: %p\n", array1);
  printf("array2: %p\n", array2);

  return 0;
}

Output do Exemplo 3:

Alocação inicial
array1: 0x16836b0
array2: 0x16836e0

Removendo array1 e realocando array2
array1: (nil)
array2: 0x16836e0

Obs: se analisarem os endereços retornados, verão que eles não se crusam, mas novamente, isso é por conta de detalhes de como o SO expõe esses endereços da heap para o processo, que não tenho conhecimento suficiente pra explicar como funciona

Acredito que a maioria das linguagens não adota a estratégia do realloc por ser bem complicada de gerenciar. Acho que é como a herança múltipla em OOP, onde a maioria das linguagens prefere não permitir para mitigar possíveis problemas.

Espero que eu tenha contribuído pra discussão e que tenham gostado da explicação. Gostaria de saber onde estou errado também e, principalmente, sobre esses detalhes de como o SO expõe os endereços de memória pro processo.

Carregando publicação patrocinada...
3

Essa explicação cabe e está correta.

Vou tentar deixar mais claro o que eu disse. De fato existe uma situação que você pode interpretar que o objeto pode aumentar de tamanho, mas nada garante que isso vai acontecer.

Como o nome diz, o realloc() realoca o espaço onde estava o objeto, conforme meu texto expllica. Inclsuive essa função sequer modifica o objeto, só trata da alocação de memória. Ele copiará o dado para outro local, portanto terá um novo objeto. Em certo momento haverá os dois objetos na memória. Ele não mexe no objeto antigo de forma alguma.

Como agora provavelmente tem mais espaço (no exemplo que pediu para realocar para um espaço maior) então poderá fazer com que o novo objeto colocado ali será maior que o anterior. Mas é outro objeto, não é o mesmo. Pode ser idêntico, mas a cópia já garante que será outro objeto.

Conforme eu falei, é concreto. Se você tem um monte de caixas no caminhão, todas adjacentes às outras sem espaço algum sobrando entre elas, só tem espaço no fim do baú, se elas tinham 10cm3 e passa ter 12cm3, como isso é possível? O único jeito é pegar um outro espaço em local vazio para colcoar essa caixa maior, não dá para colocar onde estava aquele objeto, não tem espaço. Você pode até tirar a caixa menor de lá, mas o espaço ficará lá, até que alguém coloque outra caixa no mesmo lugar, mas essa nova caixa terá 10cm3 ou menos.

O realloc() realoca em todas as situações? Não tem nada que mande ele fazer isso, e de fato algumas implementações não mudam de lugar quando o que está pedindo de alocação tem o mesmo tamanho ou menor. Para tamanhos maiores é impossível fisicamente não mudar de lugar, a não ser que se saiba que tem um espaço sobrando ali. Nem sempre se sabe, mas se souber também é uma situação que poderá não mudar de lugar.

Só note que isso é uma otimização, não é garantido que aconteça. Se você pediu um espaço maior e conseguiu deixar no mesmo lugar você já tinha o espaço maior.

Então se você tiver espaço entre as caixas é possível colocar uma caixa maior que caiba no espaço deixado ali. Sempre será feito assim? Não. Não é problema seu se vai acontecer ou não, não conte com isso, interprete que sempre haverá a relocação da caixa. Nunca conte que seja assim só porque fez um teste e deu esse resultado.

Fiat 147 todo detonado andando pelas ruas

O realloc() sequer é sobre o objeto e sim sobre a alocação. Em C a alocação e o objeto quase se confudem. Se você tem o espaço para aumentar o objeto pode interpretar que o tamanho real do objeto é todo o espaço disponível e ele não é aumentado. O aumento real só ocorre quando se cria uma nova alocação/objeto.

Inclusive nada impede de você aumentar o tamanho do objeto sem o realloc(). C tem dessas. Só que eu não considero isso porque você está corrompendo a memória e potencialmente está sobrescrevendo em cima de outro objeto.

Se eu não posso garantir que será sempre assim, eu prefiro considerar que o aumento só ocorre, garantidamente, criando um novo objeto, o resto é lucro.