Quando devo usar malloc em C e quando não?

94

Eu entendo como malloc () funciona. Minha pergunta é, verei coisas assim:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Omiti a verificação de erros por uma questão de brevidade. Minha pergunta é, você não pode simplesmente fazer o acima, inicializando um ponteiro para algum armazenamento estático na memória? possivelmente:

char *some_memory = "Hello World";

Em que ponto você realmente precisa alocar a memória em vez de declarar / inicializar os valores que você precisa reter?

randombits
fonte
5
Re: Eu omiti a verificação de erros por uma questão de brevidade - infelizmente, muitos programadores omitem a verificação de erros porque eles não percebem que malloc()pode falhar!
Andrew

Respostas:

132
char *some_memory = "Hello World";

está criando um ponteiro para uma constante de string. Isso significa que a string "Hello World" estará em algum lugar na parte somente leitura da memória e você terá apenas um ponteiro para ela. Você pode usar a string como somente leitura. Você não pode fazer alterações nele. Exemplo:

some_memory[0] = 'h';

Está procurando problemas.

Por outro lado

some_memory = (char *)malloc(size_to_allocate);

está alocando uma matriz char (uma variável) e alguns pontos de memória para essa memória alocada. Agora, esse array é tanto de leitura quanto de gravação. Agora você pode fazer:

some_memory[0] = 'h';

e o conteúdo do array muda para "hello World"

codadicto
fonte
19
Só para esclarecer, por mais que eu goste dessa resposta (eu dei a você +1), você pode fazer o mesmo sem malloc () usando apenas um array de caracteres. Algo como: char some_memory [] = "Olá"; alguma_memória [0] = 'W'; também funcionará.
randombits
19
Você está certo. Você pode fazer isso. Quando você usa malloc (), a memória é alocada dinamicamente em tempo de execução, então você não precisa corrigir o tamanho do array em tempo de compilação também você pode aumentá-lo ou diminuí-lo usando realloc () Nenhuma dessas coisas pode ser feita quando você faz: char some_memory [] = "Olá"; Aqui, embora você possa alterar o conteúdo do array, seu tamanho é fixo. Portanto, dependendo de suas necessidades, você pode usar uma das três opções: 1) ponteiro para char const 2) array alocado dinamicamente 3) tamanho fixo, array alocado em tempo de compilação.
codaddict
Para enfatizar que é somente leitura, você deve escrever const char *s = "hi";Isso não é realmente exigido pelo padrão?
Até Theis
@Till, não porque você declarou um ponteiro inicializado com o endereço base da string literal "hi". s podem ser reatribuídos perfeitamente legalmente para apontar para um caractere não const. Se você quiser um ponteiro constante para uma string somente leitura, você precisaconst char const* s;
Rob11311
38

Para esse exemplo exato, malloc é de pouca utilidade.

O principal motivo pelo qual o malloc é necessário é quando você tem dados que devem ter uma vida útil diferente do escopo do código. Seu código chama malloc em uma rotina, armazena o ponteiro em algum lugar e, eventualmente, chama free em uma rotina diferente.

Um motivo secundário é que C não tem como saber se há espaço suficiente restante na pilha para uma alocação. Se seu código precisa ser 100% robusto, é mais seguro usar malloc porque então seu código pode saber se a alocação falhou e lidar com ela.

R Samuel Klatchko
fonte
4
Os ciclos de vida da memória e a questão relacionada de quando e como desalocá-la são uma questão importante com muitas bibliotecas e componentes de software comuns. Eles normalmente têm uma regra bem documentada: "Se você passar um ponteiro para esta de minhas rotinas, você precisa malhar. Vou manter o controle e liberá-la quando terminar. " Uma fonte comum de bugs desagradáveis ​​é passar um ponteiro para a memória alocada estaticamente para essa biblioteca. Quando a biblioteca tenta liberá-lo (), o programa trava. Recentemente, passei muito tempo consertando um bug como aquele que outra pessoa escreveu.
Bob Murphy
Você está dizendo que a única vez que malloc () é usado de forma prática, é quando há um segmento de código que será chamado várias vezes durante a vida do programa que será chamado várias vezes e precisa ser 'limpo', uma vez que malloc () vem acompanhado de grátis ()? Por exemplo, em um jogo como a roda da fortuna, onde depois de adivinhar e colocar a entrada em um array de char designado, esse array de tamanho malloc () pode ser liberado para a próxima estimativa?
Smith Will Suffice
O tempo de vida dos dados é de fato a verdadeira razão para usar o malloc. Suponha que um tipo de dado abstrato seja representado por um módulo, ele declara um tipo de lista e rotinas para adicionar / excluir itens da lista. Esses valores de item precisam ser copiados para a memória alocada dinamicamente.
Rob11311
@Bob: esses bugs desagradáveis, faça a convenção de que o alocador libera memória muito superior, afinal você pode estar reciclando. Suponha que você alocou memória com calloc para melhorar a localidade das referências, o que expõe a natureza quebrada dessas bibliotecas, porque você precisa chamar gratuitamente apenas uma vez para todo o bloco. Felizmente, não tive que usar bibliotecas que especificam a memória para ser 'malloc-ed', não é uma tradição do POSIX e muito provavelmente seria considerada um bug. Se eles "sabem" que você tem que usar malloc, por que a rotina da biblioteca não faz isso por você?
Rob11311
17

malloc é uma ferramenta maravilhosa para alocar, realocar e liberar memória em tempo de execução, em comparação com declarações estáticas como seu exemplo hello world, que são processadas em tempo de compilação e, portanto, não podem ter seu tamanho alterado.

Malloc é, portanto, sempre útil quando você lida com dados de tamanho arbitrário, como ler o conteúdo do arquivo ou lidar com sockets, e você não está ciente do comprimento dos dados a processar.

Claro, em um exemplo trivial como o que você deu, malloc não é a mágica "ferramenta certa para o trabalho certo", mas para casos mais complexos (criando uma matriz de tamanho arbitrário em tempo de execução, por exemplo), é a única maneira de ir.

Moritz
fonte
7

Se você não sabe o tamanho exato da memória que precisa usar, você precisa de alocação dinâmica ( malloc). Um exemplo pode ser quando um usuário abre um arquivo em seu aplicativo. Você vai precisar ler o conteúdo do arquivo na memória, mas é claro que você não sabe o tamanho do arquivo com antecedência, já que o usuário seleciona o arquivo no local, em tempo de execução. Então, basicamente, você precisa mallocquando não sabe o tamanho dos dados com os quais está trabalhando com antecedência. Pelo menos esse é um dos principais motivos de uso malloc. Em seu exemplo com uma string simples que você já sabe o tamanho em tempo de compilação (mais você não quer modificá-la), não faz muito sentido alocá-la dinamicamente.


Um pouco fora do assunto, mas ... você tem que ter muito cuidado para não criar vazamentos de memória ao usar malloc. Considere este código:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Você vê o que há de errado com este código? Há uma declaração de retorno condicional entre malloce free. Pode parecer bom no início, mas pense nisso. Se houver um erro, você retornará sem liberar a memória alocada. Esta é uma fonte comum de vazamentos de memória.

É claro que este é um exemplo muito simples e é muito fácil ver o erro aqui, mas imagine centenas de linhas de código repletas de ponteiros, mallocs, frees e todos os tipos de tratamento de erros. As coisas podem ficar complicadas muito rápido. Esta é uma das razões pelas quais prefiro C ++ moderno em vez de C em casos aplicáveis, mas esse é um outro tópico.

Portanto, sempre que usar malloc, certifique-se de que sua memória seja o mais provável freepossível.

adam10603
fonte
Excelente exemplo! Muito bem ^ _ ^
Musa Al-hassy
6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

é ilegal, os literais de string são const.

Isso alocará uma matriz de caracteres de 12 bytes na pilha ou globalmente (dependendo de onde for declarado).

char some_memory[] = "Hello World";

Se você quiser deixar espaço para outras manipulações, pode especificar que o tamanho da matriz seja maior. (Porém, não coloque 1 MB na pilha.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);
efêmero
fonte
5

Uma razão pela qual é necessário alocar a memória é se você deseja modificá-la em tempo de execução. Nesse caso, um malloc ou um buffer na pilha pode ser usado. O exemplo simples de atribuição de "Hello World" a um ponteiro define a memória que "normalmente" não pode ser modificada em tempo de execução.

Mark Wilkins
fonte