Especificamente, o que há de perigoso em lançar o resultado de malloc?

86

Agora, antes que as pessoas comecem a marcar isso como um duplicado, li tudo o que se segue, nenhum deles fornece a resposta que procuro:

  1. C FAQ: O que há de errado em lançar o valor de retorno de malloc?
  2. SO: Devo converter explicitamente o valor de retorno de malloc ()?
  3. SO: Ponteiros desnecessários em C
  4. SO: Eu lanço o resultado de malloc?

Tanto o C FAQ quanto muitas respostas às perguntas acima citam um erro misterioso que malloco valor de retorno do casting pode ocultar; entretanto, nenhum deles dá um exemplo específico de tal erro na prática. Agora preste atenção que eu disse erro , não aviso .

Agora com o seguinte código:

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

int main(int argc, char** argv) {

    char * p = /*(char*)*/malloc(10);
    strcpy(p, "hello");
    printf("%s\n", p);

    return 0;
}

Compilar o código acima com gcc 4.2, com e sem o elenco, dá os mesmos avisos, e o programa é executado corretamente e fornece os mesmos resultados em ambos os casos.

anon@anon:~/$ gcc -Wextra nostdlib_malloc.c -o nostdlib_malloc
nostdlib_malloc.c: In function ‘main’:
nostdlib_malloc.c:7: warning: incompatible implicit declaration of built-in function ‘malloc’
anon@anon:~/$ ./nostdlib_malloc 
hello

Então, alguém pode dar um exemplo de código específico de um erro de compilação ou tempo de execução que pode ocorrer por causa do mallocvalor de retorno do casting , ou isso é apenas uma lenda urbana?

Editar Eu encontrei dois argumentos bem escritos sobre este assunto:

  1. A favor do Casting: CERT Advisory: Converta imediatamente o resultado de uma chamada de função de alocação de memória em um ponteiro para o tipo alocado
  2. Contra a transmissão (erro 404 de 14/02/2012: use a cópia do Internet Archive Wayback Machine de 27/01/2010. {18/03/2016: "A página não pode ser rastreada ou exibida devido ao robots.txt."})
Robert S. Barnes
fonte
6
lançar voidponteiros permite compilar o código como C ++; algumas pessoas dizem que é um recurso, eu diria que é um bug;)
Christoph
1
também, leia os comentários do primeiro de seus links, pois descreve o que você deve fazer em vez de lançar: securecoding.cert.org/confluence/display/seccode/…
Christoph
3
Vou seguir o conselho do CERT para incluir o elenco. Além disso, não vou esquecer de incluir stdlib.h, nunca. :)
Abhinav
1
Aqui está um exemplo de SO de um erro de tempo de execução de compilação devido ao mallocvalor de retorno do casting: casting para int*um arco de 64 bits.
John_West
1
esta questão Cnão está marcada C++(são duas línguas diferentes). Portanto, qualquer discussão (como em algumas das respostas) não é relevante para esta questão.
user3629249

Respostas:

66

Você não receberá um erro do compilador , mas um aviso do compilador . Como dizem as fontes que você cita (especialmente a primeira ), você pode obter um erro de tempo de execução imprevisível ao usar o elenco sem incluirstdlib.h .

Portanto, o erro do seu lado não é o elenco, mas o esquecimento de incluir stdlib.h. Os compiladores podem assumir que mallocé um retorno de função int, portanto, convertendo o void*ponteiro realmente retornado por mallocpara inte, em seguida, para o seu tipo de ponteiro devido à conversão explícita. Em algumas plataformas, os intponteiros podem ocupar diferentes números de bytes, portanto, as conversões de tipo podem levar à corrupção de dados.

Felizmente, os compiladores modernos fornecem avisos que apontam para o erro real. Veja a gccsaída que você forneceu: avisa que a declaração implícita ( int malloc(int)) é incompatível com a embutida malloc. Então gccparece saber mallocmesmo sem stdlib.h.

Deixar de lado o elenco para evitar esse erro é basicamente o mesmo raciocínio que escrever

if (0 == my_var)

ao invés de

if (my_var == 0)

já que o último pode levar a um erro sério se for confundido =e ==, enquanto o primeiro levaria a um erro de compilação. Pessoalmente, prefiro o último estilo, pois reflete melhor minha intenção e não costumo cometer esse erro.

O mesmo é verdadeiro para converter o valor retornado por malloc: Prefiro ser explícito na programação e geralmente faço uma verificação dupla para incluir os arquivos de cabeçalho para todas as funções que utilizo.

Ferdinand Beyer
fonte
2
Parece que, como o compilador avisa sobre a declaração implícita incompatível, isso não é um problema, desde que você preste atenção aos avisos do compilador.
Robert S. Barnes,
4
@Robert: sim, dadas certas suposições sobre o compilador. Quando as pessoas estão dando conselhos sobre a melhor forma de escrever C em geral , elas não podem presumir que a pessoa que está recebendo o conselho está usando uma versão recente do gcc.
Steve Jessop,
4
Ah, e a resposta à segunda pergunta é que o chamador contém o código para obter o valor de retorno (que ele pensa ser um int) e convertê-lo em T *. O receptor apenas escreve o valor de retorno (como um vazio *) e retorna. Portanto, dependendo da convenção de chamada: int Returns e void * Returns podem ou não estar no "mesmo lugar" (registro ou slot de pilha); int e void * podem ou não ter o mesmo tamanho; a conversão entre os dois pode ou não ser um ambiente autônomo. Portanto, pode "simplesmente funcionar" ou o valor pode estar corrompido (alguns bits perdidos, talvez) ou o chamador pode pegar o valor completamente errado.
Steve Jessop,
1
@RobertS.Barnes atrasado para a festa, mas: O valor de retorno geralmente não faz parte da assinatura da função, nem mesmo em C ++. O vinculador apenas gera um salto para um símbolo, isso é tudo.
Peter - Reintegrar Monica
3
Você pode obter um erro de tempo de execução imprevisível ao usar o elenco sem incluir stdlib.h . Isso é verdade, mas não incluir stdlib.hjá é um erro por si só, mesmo se você receber apenas avisos de "declaração implícita".
Jabberwocky
45

Um dos bons argumentos de nível superior contra a projeção do resultado de mallocmuitas vezes não é mencionado, embora, em minha opinião, seja mais importante do que os problemas de nível inferior bem conhecidos (como truncar o ponteiro quando a declaração está faltando).

Uma boa prática de programação é escrever código, que seja o mais independente de tipo possível. Isso significa, em particular, que os nomes de tipo devem ser mencionados no código o menos possível ou, melhor ainda, não devem ser mencionados. Isso se aplica a casts (evite casts desnecessários), tipos como argumentos de sizeof(evite usar nomes de tipo em sizeof) e, geralmente, todas as outras referências a nomes de tipo.

Os nomes de tipo pertencem às declarações. Tanto quanto possível, os nomes de tipo devem ser restritos a declarações e apenas a declarações.

Deste ponto de vista, este pedaço de código é ruim

int *p;
...
p = (int*) malloc(n * sizeof(int));

e isso é muito melhor

int *p;
...
p = malloc(n * sizeof *p);

não simplesmente porque "não lança o resultado de malloc", mas sim porque é independente de tipo (ou agnosítico de tipo, se você preferir), porque se ajusta automaticamente a qualquer tipo que pseja declarado com, sem exigir qualquer intervenção de o usuário.

Formiga
fonte
Fwiw, acho que este é mais ou menos o mesmo motivo: stackoverflow.com/questions/953112/… mas focado na independência de tipo ao invés de DIY. Claro que o primeiro decorre do segundo (ou vice-versa), então pelo menos é mencionado algumas vezes . :)
desenrole
5
@unwind você provavelmente quer dizer DRY ao invés de DIY
kratenko
18

Presume-se que funções não prototipadas retornem int.

Então você está lançando um intpara um ponteiro. Se os ponteiros forem mais largos do que ints em sua plataforma, esse é um comportamento altamente arriscado.

Além disso, é claro, algumas pessoas consideram os avisos como erros, ou seja, o código deve ser compilado sem eles.

Pessoalmente, acho que o fato de você não precisar converter void *para outro tipo de ponteiro é um recurso em C e considero o código que está quebrado.

desanuviar
fonte
14
Tenho a convicção de que o compilador sabe mais sobre a linguagem do que eu, então, se ele me avisa sobre algo, presto atenção.
György Andrasek
3
Em muitos projetos, o código C é compilado como C ++, onde você precisa converter o void*.
laalto
nit: " por padrão , funções não prototipadas são assumidas para retornar int." - Quer dizer que é possível alterar o tipo de retorno de funções não prototipadas?
pmg
1
@laalto - É, mas não deveria ser. C é C, não C ++, e deve ser compilado com um compilador C, não um compilador C ++. Não há desculpa: o GCC (um dos melhores compiladores C que existe) roda em quase todas as plataformas imagináveis ​​(e também gera código altamente otimizado). Que razões você possivelmente teria para compilar C com um compilador C ++, além de preguiça e padrões frouxos?
Chris Lutz,
3
Exemplo de código que você pode querer compilar como C e C ++: #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif. Agora, por que você deseja chamar malloc em uma função embutida estática, eu realmente não sei, mas cabeçalhos que funcionam em ambos dificilmente são desconhecidos.
Steve Jessop,
11

Se você fizer isso ao compilar no modo de 64 bits, o ponteiro retornado será truncado para 32 bits.

EDIT: Desculpe por ser muito breve. Aqui está um fragmento de código de exemplo para fins de discussão.

a Principal()
{
   char * c = (char *) malloc (2);
   printf ("% p", c);
}

Suponha que o ponteiro de heap retornado seja algo maior do que o representável em um int, digamos 0xAB00000000.

Se malloc não for prototipado para retornar um ponteiro, o valor int retornado inicialmente estará em algum registro com todos os bits significativos definidos. Agora o compilador diz, "ok, como faço para converter e int em um ponteiro". Isso vai ser uma extensão de sinal ou extensão zero dos 32 bits de ordem inferior que foi dito que malloc "retorna" ao omitir o protótipo. Como int é assinado, acho que a conversão será a extensão de sinal, que neste caso converterá o valor para zero. Com um valor de retorno de 0xABF0000000, você obterá um ponteiro diferente de zero que também causará diversão quando você tentar desreferenciá-lo.

Peeter Joot
fonte
1
Você poderia explicar em detalhes como isso ocorreria?
Robert S. Barnes,
5
Acho que Peeter Joot estava descobrindo que "Por padrão, funções não prototipadas devem retornar int" sem incluir stdlib.h, e sizeof (int) é de 32 bits, enquanto sizeof (ptr) é 64.
Teste de
4

Uma regra de software reutilizável:

No caso de escrever uma função inline na qual usou malloc (), a fim de torná-la reutilizável para código C ++ também, faça uma conversão de tipo explícita (por exemplo, (char *)); caso contrário, o compilador reclamará.

Teste
fonte
esperançosamente, com a (recente) inclusão de otimizações de tempo de link no gcc (veja gcc.gnu.org/ml/gcc/2009-10/msg00060.html ), declarar funções inline em arquivos de cabeçalho não será mais necessário
Christoph
você tem ideias ruins. você está ciente de que o que é portátil e multiplataforma entre diferentes compiladores / versões / arquiteturas? ok, você não pode. então o que reutilizável significa?
Teste de
2
ao escrever C ++, malloc / free NÃO é a metodologia certa. Em vez disso, use new / delete. IE, não deve haver chamadas / nada / zero para malloc / free no código C ++
user3629249
3
@ user3629249: Ao escrever uma função que precisa ser utilizável a partir de dentro , quer código C ou código C ++, usando malloc/ freepara ambos tende a ser melhor do que tentar usar mallocem C e newem C ++, especialmente se estruturas de dados são compartilhados entre C e C ++ código e existe a possibilidade de que um objeto seja criado em código C e lançado em código C ++ ou vice-versa.
supercat
3

Um ponteiro void em C pode ser atribuído a qualquer ponteiro sem uma conversão explícita. O compilador dará um aviso, mas pode ser reutilizável em C ++ pela conversão de tipo malloc()para o tipo correspondente. Sem a conversão de tipo, também pode ser usado em C , porque C não é uma verificação de tipo estrita . Mas C ++ é estritamente verificação de tipo, portanto, é necessário converter malloc()em C ++.

Yogeesh HT
fonte
Se você usa malloc em C ++, é melhor ter um bom motivo! ; p
antred