E se a "senhorita" realloc falhar? [RESOLVIDO]

1. E se a "senhorita" realloc falhar? [RESOLVIDO]

Perfil removido
removido

(usa Nenhuma)

Enviado em 13/02/2019 - 00:15h

Na man page da função realloc diz o seguinte sobre o seu valor de retorno:


The realloc() function returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different from ptr, or NULL if the request fails. If size was equal to 0, either NULL or a pointer suitable to be passed to free() is returned. If realloc() fails the original block is left untouched; it is not freed or moved.


Beleza! Diz que se realloc falhar o bloco de memória alocado permanecerá intacto. Mas quando eu saberei que essa misera falhou? Pois tá dizendo ali que só retorna NULL caso o parâmetro size receba 0 *_* (ou será que entendi errado?).

____________________________________________________________________________________________

EXTRA: Essa verificação abaixo faz sentido?


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

int main(void){

int *p=malloc(50*sizeof(int));

//..código..

int *sp=realloc(p, 100*sizeof(int));

if(sp!=NULL){

p=sp;

//..código...

free(p);

p=NULL;
sp=NULL; //Essa atribuição faz algum sentido?

}else{

printf("\n\n* www.youtube.com/watch?v=PTVusldCCEo ... realloc() falhou!!!\n\n");

free(p); //Isso é válido?

exit(-1);
}

return 0;
}


Pergunta extra: aquela chamada para free antes da função exit é válida?

Não me critiquem se não fez sentido!
E sim esse professor > https://youtu.be/vEMTGkANXM4?t=292



  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 15/02/2019 - 16:00h

mcgivern escreveu:

Na man page da função realloc diz o seguinte sobre o seu valor de retorno:

The realloc() function returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different from ptr, or NULL if the request fails. If size was equal to 0, either NULL or a pointer suitable to be passed to free() is returned. If realloc() fails the original block is left untouched; it is not freed or moved.


Beleza! Diz que se realloc falhar o bloco de memória alocado permanecerá intacto. Mas quando eu saberei que essa misera falhou? Pois tá dizendo ali que só retorna NULL caso o parâmetro size receba 0 *_* (ou será que entendi errado?).


A forma canônica de fazer é usar um segundo ponteiro, e verificar o valor do ponteiro.
int *ponteiro=malloc(1000 * sizeof *ponteiro);
/* ... */
void *temp_pont=realloc(ponteiro, 2000 * sizeof *ponteiro);
if(temp_pont!=NULL){
ponteiro=temp_pont;
// Note que eu não tenho de dar free em ninguém em caso de sucesso.
else{
/*
Se cair aqui, é porque deu erro de alocar o novo tamanho.

Dependendo da sua aplicação, isso pode ser um erro fatal (e o é para a
maioria das aplicações). Nesse caso, o que vai aqui pode ser a exibição
de uma mensagem de erro e uma chamada a abort() ou exit() com argu-
mento diferente de zero.
*/
//fprintf(stderr, "Falha ao alocar memória: %s.\n", strerror(errno));
//exit(1);

/*
Há casos, porém, em que é preferível manter o ponteiro original, que garan-
tidamente ainda permanece válido (temp_pont==NULL, mas ponteiro continua
com seu valor antigo), e seguir com a execução do programa.
*/
}

/* ... segue o código, usan ponteiro e/ou *ponteiro... */

// Ao final, libera a área (re)alocada.
free(ponteiro);


EXTRA: Essa verificação abaixo faz sentido?

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

int main(void){

int *p=malloc(50*sizeof(int));

//..código..

int *sp=realloc(p, 100*sizeof(int));

if(sp!=NULL){

p=sp;

//..código...

free(p);

p=NULL;
sp=NULL; //Essa atribuição faz algum sentido?


Ela é uma segurança para você de que você não vai usar o valor antigo do ponteiro depois de ele ter sido desalocado. Mas, a rigor, depois de chamar free(), você não teria necessariamente de ter zerado nenhum desses dois ponteiros.


}else{

printf("\n\n* www.youtube.com/watch?v=PTVusldCCEo ... realloc() falhou!!!\n\n");

free(p); //Isso é válido?


Sim. Só lembro que você não necessariamente vai precisar sair. Pode ser que interesse à sua aplicação ficar usando a área previamente alocada e os dados já nela contidos.

E, por outro lado, se você decidir sair do programa, possivelmente não precisará se preocupar com a desalocação do ponteiro, pois o fim do programa deveria garantir a liberação de tudo o que tiver sido alocado durante sua execução (mas veja observação ao final da mensagem).


exit(-1);
}

return 0;
}


Pergunta extra: aquela chamada para free antes da função exit é válida?


Se o ponteiro apontar para alguma coisa que tenha sido previamente retornada por malloc(), realloc() ou calloc() e que já não tenha sido liberada com free() ou realocada para outro endereço com realloc(), então é valida, sim. Como dito acima, possivelmente desnecessária se o programa vai terminar logo em seguida, mas certamente válida.

Não me critiquem se não fez sentido!
E sim esse professor > https://youtu.be/vEMTGkANXM4?t=292


Até que eu achei a aula dele bem razoável, muito, MUITO melhor do que outras aulas via Youtube que já foram apresentadas aqui (só não gostei daquela musiquinha 8-bit na abertura do vídeo, hehehe...).

E o que ele falou sobre desalocar foi, para mim, bem claro: quando você não for mais precisar do ponteiro, deve chamar free() sobre ele. No exemplo que ele mostrou, esse momento ocorria imediatamente antes do fim do programa, mas somente porque era um exemplo curto, cuja intenção era mostrar a existência e o funcionamento básico de realloc(). Num caso geral e num programa real, que não seja de mera demonstração, é importante lembrar de desalocar o que tiver sido alocado, para não correr o risco de ter vazamentos de memória.

Mas embora eu mesmo tenha dito acima que uma chamada a exit() logo depois de free() a torna desnecessária, vale lembrar que essa é uma verdade nos nossos sistemas comuns do dia a dia, porque o sistema operacional (UNIX-like ou Windows, que é o que a maioria de nós usa) associa as alocações de memória a cada processo. Pode ser que em outros sistema, como sistemas embarcados, alguns casos anômalos no velho MS-DOS, máquinas sem MMU ou mesmo bibliotecas que usem versões customizadas de malloc() e família, um programa que termine não devolva realmente sua memória ao ambiente de execução. Assim sendo, um programa que tenha o intuito de ser realmente portável poderia considerar um mundo em que o ambiente de execução pode não reaver memória que não lhe seja explicitamente devolvida, mesmo que o programa termine. Nesses casos, para cada ponteiro (re)alocado tem de haver uma liberação correspondente.

Se você não quiser pecar por omissão num ambiente que você talvez ainda não conheça, é razoável colocar free() assim que puder, mesmo que seja antes de exit().


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)

3. Re: E se a

José
DeuRuimDotCom

(usa Linux Mint)

Enviado em 13/02/2019 - 11:20h

Estou meio enferrujado, mas me parece que seu código tem sentido sim.
Todavia, acho que basta isso:

if((p=realloc(p, 100*sizeof(int))) == NULL){
printf("\n\n* www.youtube.com/watch?v=PTVusldCCEo ... realloc() falhou!!!\n\n");
exit(-1);}


A função free é válida sim e serve para liberar e evitar armazenamento de memória. "p=NULL" também para evitar ponteiros selvagens, porque mesmo depois de free(p), p não vai apontar pra NULL automaticamente, não.


4. Re: E se a

José
DeuRuimDotCom

(usa Linux Mint)

Enviado em 15/02/2019 - 21:05h

Olá paulo 1205,
Muito boa sua explanação. Estudei sozinho linguagem C uns tempos atrás por curiosidade, li alguns livros (o do DAMAS por ex.), mas larguei porque não tive onde usá-la. Todavia, permanecendo o interesse, peço licença, já que o tópico não é meu, para te colocar algumas dúvidas:
paulo1205 escreveu:
A forma canônica de fazer é usar um segundo ponteiro, e verificar o valor do ponteiro.

Sim, porque pode ser que se pretenda usar o bloco de memória que permanece ainda que falhe realloc. Entretanto, como o código do rapaz previa, retornado NULL, a execução de exit(-1), não vi sentido em lançar mão doutro ponteiro.

paulo1205 escreveu:
Mas, a rigor, depois de chamar free(), você não teria necessariamente de ter zerado nenhum desses dois ponteiros.

Não seria, em geral, uma boa prática de programação "nulificar" o ponteiro em vez de deixá-lo apontado? Por segurança, entre outros motivos?

paulo1205 escreveu:
Se você não quiser pecar por omissão num ambiente que você talvez ainda não conheça, é razoável colocar free() assim que puder, mesmo que seja antes de exit().

Muito bom saber disso. Imaginava que, com o fim do programa, as porções de memória a ele relacionadas seriam imediatamente desmontadas, em todos os casos, independentemente do ambiente.




5. Re: E se a

Paulo
paulo1205

(usa Ubuntu)

Enviado em 17/02/2019 - 01:38h

DeuRuimDotCom escreveu:

paulo1205 escreveu:
Mas, a rigor, depois de chamar free(), você não teria necessariamente de ter zerado nenhum desses dois ponteiros.

Não seria, em geral, uma boa prática de programação "nulificar" o ponteiro em vez de deixá-lo apontado? Por segurança, entre outros motivos?


Tem suas vantagens, sim. Se você o anular, e sem querer aplicar sobre ele uma segunda chamada a free() ou uma nova chamada a realloc(), vai se proteger dos erros imprevisíveis que essas chamadas indevidas poderiam provocar. Mas considere o seguinte:

  • se você está tentando reaproveitar um valor de ponteiro (não a variável em si; repare que tanto realloc() quanto free() recebem o parâmetro do ponteiro por valor, não por referência) que designa uma área de memória que foi invalidada, seu programa necessariamente tem um bug, e esse bug deveria ser corrigido;

  • se o seu programa não tiver o bug acima, a operação de zerar o ponteiro se torna redundante;

  • se você zerar o ponteiro mesmo assim, você elimina apenas uma parte dos possíveis bugs decorrentes de usar um ponteiro para memória que já foi liberada: se em vez de se ter uma operação de realocação ou liberação se tivesse uma tentativa de acesso a elemento apontado, zerar a variável ponteiro não ajudaria em nada.

paulo1205 escreveu:
Se você não quiser pecar por omissão num ambiente que você talvez ainda não conheça, é razoável colocar free() assim que puder, mesmo que seja antes de exit().

Muito bom saber disso. Imaginava que, com o fim do programa, as porções de memória a ele relacionadas seriam imediatamente desmontadas, em todos os casos, independentemente do ambiente.


Eu procurei pesquisar a documentação de malloc() e de exit() para ver se esse tipo de garantia seria exigido. Não encontrei a exigência em lugar nenhum.

E eu lembro vagamente de já ter vivido problemas de vazamento de memória mesmo depois de encerrar o programa na época em que eu programava em MS-DOS. Só não lembro o contexto exato, e por isso mesmo eu falei situações anômalas, não corriqueiras (o DOS certamente não teria reinado por tanto tempo se todo programa vivesse vazando memória).


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


6. Re: E se a

José
DeuRuimDotCom

(usa Linux Mint)

Enviado em 17/02/2019 - 22:03h

paulo1205 escreveu:
Tem suas vantagens, sim. Se você o anular, e sem querer aplicar sobre ele uma segunda chamada a free() ou uma nova chamada a realloc(), vai se proteger dos erros imprevisíveis que essas chamadas indevidas poderiam provocar. Mas considere o seguinte:
  • se você está tentando reaproveitar um valor de ponteiro (não a variável em si; repare que tanto realloc() quanto free() recebem o parâmetro do ponteiro por valor, não por referência) que designa uma área de memória que foi invalidada, seu programa necessariamente tem um bug, e esse bug deveria ser corrigido;
  • se o seu programa não tiver o bug acima, a operação de zerar o ponteiro se torna redundante;
  • se você zerar o ponteiro mesmo assim, você elimina apenas uma parte dos possíveis bugs decorrentes de usar um ponteiro para memória que já foi liberada: se em vez de se ter uma operação de realocação ou liberação se tivesse uma tentativa de acesso a elemento apontado, zerar a variável ponteiro não ajudaria em nada.


Ótimos seus argumentos. Apenas me lembro de que quando estudei C li nalgum lugar que seria uma boa prática de programação nulificar o ponteiro após a free. Lembro-me também de que recomendavam declarar sempre o ponteiro apontado para NULL; mas como te disse, não sou programador profissional.

Na verdade, não estava nem a pensar no fato óbvio de nova e inadvertida chamada do ponteiro, até porque, como você bem disse, nulificado ou não, um bug assim ocorrerá mais por falta de atenção do programador.

Estava a pensar na seguinte situação e por favor me corrija se estiver enganado (repito que sou apenas um curioso!): O programador libera a memória com o free e intencionalmente como parte de seu programa, em momento posterior, prevê uma nova chamada do ponteiro, cogitando que a área de memória por ele apontada permanecerá constante. Todavia, o ambiente no qual o programa está rodando, sendo multitarefa, pode usar a área liberada mas ainda apontada para cumprir outros processos antes da execução da nova chamada. Pode até ser o caso de essa "falha" ser usada por softwares mal-intencionados. Nesse caso, ter em mente a anulação ou um novo apontamento como prática a ser seguida viria a calhar.

Viajei?




7. Re: E se a

Paulo
paulo1205

(usa Ubuntu)

Enviado em 18/02/2019 - 15:15h

DeuRuimDotCom escreveu:

Ótimos seus argumentos. Apenas me lembro de que quando estudei C li nalgum lugar que seria uma boa prática de programação nulificar o ponteiro após a free. Lembro-me também de que recomendavam declarar sempre o ponteiro apontado para NULL; mas como te disse, não sou programador profissional.


O argumento provavelmente vai na linha de não deixar a variável com um valor indefinido (no caso do valor inicial) ou sabidamente inválido (no caso de um ponteiro que acabou de ser liberado), mas sim com um valor que pelo menos tem um comportamento bem descrito (ainda que não seja válido para ser derreferenciado).

Mas observe que isso só ajuda a evitar acidentes com situações que são, elas próprias, também acidentes (em bom Português: são erros, mesmo).

E existe um contra-argumento bem óbvio, que é o fato de que o código extra provocado pela garantia dos valores seguros dificulta a manutenção do código, trazendo outros riscos que o programa mais simples não correria. Por exemplo: a versão “segura” da desalocação implica dois comandos para cada ponteiro desalocado (um para chamar free() e outro para zerar o ponteiro), o que significa que em qualquer manutenção que essa desalocação tiver de sofrer ao longo do ciclo de vida do programa, quer seja troca de posição, remoção, (re)implementação ou substituição por outra operação (imagine o caso de trocar free() por realloc()), será necessário atuar sobre dois comandos, em lugar de apenas um. O risco é a pessoa que estiver dando manutenção no código, que não necessariamente será seu autor original, e pode não ter a cultura de “desalocação segura”, ou até mesmo tê-la, mas não lembrar ou prestar atenção a sua presença no programa) esquecer de mexer no segundo comando, indevidamente deixando (ou omitindo) um código espúrio (ou requerido) num determinado local do programa.

Na verdade, não estava nem a pensar no fato óbvio de nova e inadvertida chamada do ponteiro, até porque, como você bem disse, nulificado ou não, um bug assim ocorrerá mais por falta de atenção do programador.


Eu arrisco o palpite de que a maioria dos bugs é por falta de atenção. free() ou realloc() usando um valor de ponteiro já liberado certamente só ocorrerá por descuido, não projeto.

Estava a pensar na seguinte situação e por favor me corrija se estiver enganado (repito que sou apenas um curioso!): O programador libera a memória com o free e intencionalmente como parte de seu programa, em momento posterior, prevê uma nova chamada do ponteiro, cogitando que a área de memória por ele apontada permanecerá constante. Todavia, o ambiente no qual o programa está rodando, sendo multitarefa, pode usar a área liberada mas ainda apontada para cumprir outros processos antes da execução da nova chamada. Pode até ser o caso de essa "falha" ser usada por softwares mal-intencionados. Nesse caso, ter em mente a anulação ou um novo apontamento como prática a ser seguida viria a calhar.


Pode, mas nem sempre de modo muito direto.

Num sistema como o MS-DOS, que não é multitarefa e multiusuário e que nem ao menos tenta proteger a memória, existe uma chance gigantesca de que dados do programa que eu executo agora não terão sido apagados da memória daqui a uma hora, quando eu já tiver saído e você já a estiver usando para rodar um scanner de dados potencialmente interessantes na memória. Nos nossos UNIX-like e Windows da vida, eu *acho* que a entrega de memória é feita de modo em que requisições de memória nova ao SO sempre entregam conteúdo zerado (nem toda alocação usa memória nova: num programa de longa duração, a memória recebida como resultado de alocação com funções da biblioteca padrão do C pode eventualmente reaproveitar memória que já tenha sido usada e liberada pelo mesmo programa em algum momento anterior).

Essa é outra daquelas áreas em que é importante saber que o comportamento pode variar de máquina para máquina. Quando eu estava na faculdade, um professor nosso, comentando sobre processamento de sinais, contava como sendo famoso um caso de um programa que parou de funcionar porque o autor assumia que alocar memória dinamicamente dava sempre acesso a conteúdo que poderia ser usado como ruído branco, mas que quando se mudou a plataforma em que o programa rodava, o conteúdo da memória entregue não era mais aleatório, e portanto não servia como ruído branco para a aplicação.

Mas mesmo em sistemas que entreguem memória zerada, existem formas indiretas de saber ou inferir conteúdo colocado nessa memória. Há os mais óbvios, como examinar o conteúdo que acaba sendo gravado em disco quando ocorre swap. Mas há também outros métodos menos óbvios — por exemplo: usar análise de tempo de resposta de memória para inferir conteúdo de cache, que por sinal é mais ou menos como a falha de hardware conhecida como Spectre, que causou terror no começo de 2018, funciona.

Existem outras técnicas de obter conteúdo de memória alheia. Contudo, meu conhecimento pessoal sobre elas é pequeno, pois nunca parei para estudar o assunto, e até as explicações para leigos não me são muito fáceis de assimilar.

Viajei?


Não necessariamente. Tudo o que você cogitou existe. Nem sempre é muito fácil, mas está longe de ser impossível, ou mesmo extremamente difícil para quem domina as técnicas corretas.


... “Principium sapientiae timor Domini, et scientia sanctorum prudentia.” (Proverbia 9:10)


8. Re: E se a "senhorita" realloc falhar? [RESOLVIDO]

José
DeuRuimDotCom

(usa Linux Mint)

Enviado em 18/02/2019 - 21:52h

paulo1205 escreveu:
E existe um contra-argumento bem óbvio, que é o fato de que o código extra provocado pela garantia dos valores seguros dificulta a manutenção do código, trazendo outros riscos que o programa mais simples não correria. Por exemplo: a versão “segura” da desalocação implica dois comandos para cada ponteiro desalocado (um para chamar free() e outro para zerar o ponteiro), o que significa que em qualquer manutenção que essa desalocação tiver de sofrer ao longo do ciclo de vida do programa, quer seja troca de posição, remoção, (re)implementação ou substituição por outra operação (imagine o caso de trocar free() por realloc()), será necessário atuar sobre dois comandos, em lugar de apenas um. O risco é a pessoa que estiver dando manutenção no código, que não necessariamente será seu autor original, e pode não ter a cultura de “desalocação segura”, ou até mesmo tê-la, mas não lembrar ou prestar atenção a sua presença no programa) esquecer de mexer no segundo comando, indevidamente deixando (ou omitindo) um código espúrio (ou requerido) num determinado local do programa.

Nem tão óbvia assim pra mim, é uma razão de fato muito forte. Difícil vislumbrar um contra-argumento que a enfraquecesse.

paulo1205 escreveu:
Num sistema como o MS-DOS, que não é multitarefa e multiusuário e que nem ao menos tenta proteger a memória, existe uma chance gigantesca de que dados do programa que eu executo agora não terão sido apagados da memória daqui a uma hora, quando eu já tiver saído e você já a estiver usando para rodar um scanner de dados potencialmente interessantes na memória. Nos nossos UNIX-like e Windows da vida, eu *acho* que a entrega de memória é feita de modo em que requisições de memória nova ao SO sempre entregam conteúdo zerado (nem toda alocação usa memória nova: num programa de longa duração, a memória recebida como resultado de alocação com funções da biblioteca padrão do C pode eventualmente reaproveitar memória que já tenha sido usada e liberada pelo mesmo programa em algum momento anterior).
Essa é outra daquelas áreas em que é importante saber que o comportamento pode variar de máquina para máquina. Quando eu estava na faculdade, um professor nosso, comentando sobre processamento de sinais, contava como sendo famoso um caso de um programa que parou de funcionar porque o autor assumia que alocar memória dinamicamente dava sempre acesso a conteúdo que poderia ser usado como ruído branco, mas que quando se mudou a plataforma em que o programa rodava, o conteúdo da memória entregue não era mais aleatório, e portanto não servia como ruído branco para a aplicação.
Mas mesmo em sistemas que entreguem memória zerada, existem formas indiretas de saber ou inferir conteúdo colocado nessa memória. Há os mais óbvios, como examinar o conteúdo que acaba sendo gravado em disco quando ocorre swap. Mas há também outros métodos menos óbvios — por exemplo: usar análise de tempo de resposta de memória para inferir conteúdo de cache, que por sinal é mais ou menos como a falha de hardware conhecida como Spectre, que causou terror no começo de 2018, funciona.
Existem outras técnicas de obter conteúdo de memória alheia. Contudo, meu conhecimento pessoal sobre elas é pequeno, pois nunca parei para estudar o assunto, e até as explicações para leigos não me são muito fáceis de assimilar.

Entendi. É uma campo fascinante esse. Como te disse, sou apenas um curioso no assunto mas é sempre muito bom encontrar alguém disposto a debater e que entenda tanto do riscado. Se não se importar, mesmo que o tópico não seja meu, surgindo dúvidas vou te importunar com elas rsrs. Valeu!







Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts