Eu transmito o resultado do malloc?


2408

Em esta pergunta , alguém sugeriu em um comentário que eu deveria não converter o resultado de malloc, ou seja,

int *sieve = malloc(sizeof(int) * length);

ao invés de:

int *sieve = (int *) malloc(sizeof(int) * length);

Por que isso seria o caso?


222
Além disso, é mais sustentável escrever peneira = malloc (tamanho de * peneira * comprimento);
William Pursell


3
As respostas aqui são um campo minado de unilateralidade. A resposta é "depende". Não é necessário fazer o programa funcionar. No entanto, alguns padrões de codificação exigem isso ... Por exemplo, consulte Padrão de codificação CERT C
Dr. Person Person II

Estranhamente, todo mundo concorda que você não escolhe NULL. (é provavelmente por isso que o C ++ introduziu nullptr: o C ++ não permite lançamentos implícitos de ponteiros)
Sapphire_Brick

Você pode, mas não precisa. Mas você precisa em C ++.
user12211554

Respostas:


2217

Não ; você não transmite o resultado, pois:

  • É desnecessário, pois void *é automaticamente e com segurança promovido para qualquer outro tipo de ponteiro nesse caso.
  • Ele adiciona confusão ao código, as conversões não são muito fáceis de ler (especialmente se o tipo de ponteiro for longo).
  • Isso faz você se repetir, o que geralmente é ruim.
  • Pode ocultar um erro se você se esquecer de incluir <stdlib.h>. Isso pode causar falhas (ou, pior, não causar uma falha até muito mais tarde em alguma parte totalmente diferente do código). Considere o que acontece se ponteiros e números inteiros tiverem tamanhos diferentes; você está ocultando um aviso ao transmitir e pode perder partes do seu endereço retornado. Nota: a partir de C99, as funções implícitas passaram de C, e esse ponto não é mais relevante, pois não há suposição automática de que as funções não declaradas retornam int.

Como esclarecimento, observe que eu disse "você não lança", e não "você não precisa transmitir". Na minha opinião, é um fracasso em incluir o elenco, mesmo se você acertar. Simplesmente não há benefícios em fazê-lo, mas vários riscos em potencial, e a inclusão do elenco indica que você não conhece os riscos.

Observe também, como os comentaristas apontam, que o acima mencionado fala sobre C direto, não C ++. Eu acredito firmemente em C e C ++ como linguagens separadas.

Para adicionar ainda mais, seu código repete desnecessariamente as informações de tipo ( int) que podem causar erros. É melhor des-referenciar o ponteiro que está sendo usado para armazenar o valor de retorno e "bloquear" os dois juntos:

int *sieve = malloc(length * sizeof *sieve);

Isso também move o lengthpara a frente para aumentar a visibilidade e descarta os parênteses redundantes sizeof; eles são necessários apenas quando o argumento é um nome de tipo. Muitas pessoas parecem não saber (ou ignorar) isso, o que torna seu código mais detalhado. Lembre-se: sizeofnão é uma função! :)


Embora mover lengthpara a frente possa aumentar a visibilidade em alguns casos raros, também se deve prestar atenção que, no caso geral, deve ser melhor escrever a expressão como:

int *sieve = malloc(sizeof *sieve * length);

Como manter o sizeofprimeiro, neste caso, garante que a multiplicação seja feita com pelo menos size_tmatemática.

Compare: malloc(sizeof *sieve * length * width)vs. malloc(length * width * sizeof *sieve)o segundo pode exceder o length * widthquando widthe lengthsão tipos menores que size_t.


21
Por favor, considere atualizar a resposta. O elenco não é mais perigoso, e repetir a si mesmo não é necessariamente uma coisa ruim (redundância pode ajudar a detectar erros).
n. 'pronomes' m.

11
Compiladores mudaram. Um compilador atualizado irá avisá-lo sobre uma declaração de malloc ausente.
n. 'pronomes' m.

55
@nm Ok. Eu acho que é ruim supor que quem está lendo aqui tem um compilador específico. Além disso, como C11 todo o conceito de "função implícita" se foi, eu não sabia disso. Ainda assim, não vejo sentido em adicionar um elenco sem sentido. Você também faz int x = (int) 12;apenas para esclarecer as coisas?
descontraia

22
@nm se lançar explicitamente um ponteiro nulo "ajudou" a resolver um bug, é mais provável que você tenha encontrado um comportamento indefinido, o que significa que o programa em questão provavelmente tem um bug ainda pior e ainda não descoberto que você ainda não encontrou. E um dia, em uma noite fria de inverno, você volta para casa do trabalho e encontra sua página do GitHub cheia de relatórios de problemas reclamando sobre demônios saindo do nariz dos usuários
Braden Best

12
@unwind Mesmo que eu concorde com você, (int)12não é comparável. 12 é um int, o elenco simplesmente não faz nada. O retval de malloc()is void *, não o tipo de ponteiro convertido. (Se não for void *. Assim, a analogia com (int)12seria (void*)malloc(…)o que ninguém está discutindo.)
Amin Negm-Awad

376

Em C, você não precisa converter o valor de retorno de malloc. O ponteiro para anular retornado por mallocé convertido automaticamente para o tipo correto. No entanto, se você deseja que seu código seja compilado com um compilador C ++, uma conversão é necessária. Uma alternativa preferida entre a comunidade é usar o seguinte:

int *sieve = malloc(sizeof *sieve * length);

o que, além disso, libera você de se preocupar em alterar o lado direito da expressão, se você alterar o tipo de sieve.

Elencos são ruins, como as pessoas apontaram. Especialmente ponteiro lança.


71
@ MAKZ Eu diria que malloc(length * sizeof *sieve)isso parece sizeofuma variável - então eu acho que malloc(length * sizeof(*sieve))é mais legível.
Michael Anderson

21
E malloc(length * (sizeof *sieve))mais legível ainda. NA MINHA HUMILDE OPINIÃO.
Toby Speight

19
@ Michael Anderson ()questão de lado, observe que o estilo sugerido mudou de ordem., Considere quando a contagem de elementos é calculada como length*width, manter o sizeofprimeiro nesse caso garante que a multiplicação seja feita com pelo menos size_tmatemática. Compare malloc(sizeof( *ptr) * length * width)vs. malloc(length * width * sizeof (*ptr))- o segundo pode estourar length*widthquando width,lengthsão tipos menores que size_t.
chux - Restabelece Monica

3
@chux não é óbvio, mas a resposta foi editado para que o meu comentário é menos pertinente - a sugestão originalmalloc(sizeof *sieve * length)
Michael Anderson

12
C não é C ++. Fingir que eles são levará a confusão e tristeza. Se você estiver usando C ++, uma conversão no estilo C também será ruim (a menos que você esteja usando um compilador C ++ muito antigo). E static_cast>()(ou reinterpret_cast<>()) não é compatível com qualquer dialeto do C.
David C.

349

Você não fundido, porque:

  • Isso torna seu código mais portátil entre C e C ++ e, como mostra a experiência do SO, muitos programadores afirmam que estão escrevendo em C quando realmente estão escrevendo em C ++ (ou C mais extensões de compilador local).
  • Não fazer isso pode ocultar um erro : observe todos os exemplos de confusão quando escrever type *versus type **.
  • A idéia de impedir que você observe que você falhou em #includeum arquivo de cabeçalho apropriado perde a floresta para as árvores . É o mesmo que dizer "não se preocupe com o fato de você ter falhado em pedir ao compilador para reclamar por não ter visto protótipos - que o irritante stdlib.h é a coisa realmente importante a lembrar!"
  • Isso força uma verificação cruzada cognitiva extra . Ele coloca o tipo (suposto) desejado ao lado da aritmética que você está fazendo para o tamanho bruto dessa variável. Aposto que você poderia fazer um estudo de SO que mostra que os malloc()bugs são detectados muito mais rapidamente quando há um lançamento. Assim como as asserções, as anotações que revelam a intenção diminuem os erros.
  • Repetir-se de uma maneira que a máquina possa verificar geralmente é uma ótima idéia. De fato, é isso que é uma afirmação, e esse uso do elenco é uma afirmação. As asserções ainda são a técnica mais geral que temos para obter o código correto, desde que Turing surgiu com a ideia há muitos anos.

39
@ulidtko Caso você não saiba, é possível escrever código que compila tanto em C como em C ++. De fato, a maioria dos arquivos de cabeçalho é assim e geralmente contém código (macros e funções embutidas). Ter um arquivo .c/ .cpppara compilar como ambos não é útil com muita frequência, mas um caso é adicionar throwsuporte ao C ++ quando compilado com o compilador C ++ (mas return -1;quando compilado com o compilador C ou o que for).
Hyde 26/03

37
Se alguém tivesse chamadas malloc embutidas em um cabeçalho, não ficaria impressionado, #ifdef __cplusplus e extern "C" {} são para esse trabalho, não adicionando conversões extras.
paulm

15
Bem, o ponto 1 é irrelevante, já que C! = C ++, os outros pontos também são triviais, se você usar a variável em sua mallocchamada: char **foo = malloc(3*sizeof(*foo));se for uma prova completa: 3 ponteiros para ponteiros char. então faça um loop e faça foo[i] = calloc(101, sizeof(*(foo[i])));. Aloque uma matriz de 101 caracteres, inicializada ordenadamente em zeros. Não é necessário elenco. altere a declaração para unsigned charou qualquer outro tipo, para que o assunto, e você ainda está bom
Elias Van Ootegem

34
Quando eu pensei que eu consegui, aí vem! Resposta fantástica. É a primeira vez aqui no StackOverflow que eu +1 duas respostas opostas! +1 Não, você não lança, e +1 Sim, você lança! RI MUITO. Vocês são ótimos. E para mim e meus alunos, eu decidi: eu elenco. O tipo de erro que os alunos cometem são mais facilmente detectados na transmissão.
Dr Beco

15
@Leushenko: Repetir a si mesmo de uma maneira que não pode ser validada pela máquina nem pela inspeção local é ruim. Repetir a si mesmo de maneiras que possam ser validadas por esses meios é menos ruim. Dado que struct Zebra *p; ... p=malloc(sizeof struct Zebra);o malloc não pode evitar informações duplicadas sobre o tipo de p, mas nem o compilador nem a inspeção de código local detectariam qualquer problema se um tipo fosse alterado, mas o outro não. Altere o código para p=(struct Zebra*)malloc(sizeof struct Zebra);e o compilador gritará se o tipo de elenco não corresponder pe a inspeção local irá revelar ... #
686

170

Como outros declararam, não é necessário para C, mas necessário para C ++. Se você acha que vai compilar seu código C com um compilador C ++, por qualquer motivo, você pode usar uma macro, como:

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

Dessa forma, você ainda pode escrevê-lo de uma maneira muito compacta:

int *sieve = NEW(int, 1);

e ele será compilado para C e C ++.


17
Como você está usando uma macro de qualquer maneira, por que você não usa newna definição de C ++?
Hosam Aly 4/03/09

63
Porque não há razão para fazê-lo. É principalmente para programas C que são compilados com um compilador C ++. Se você for usar 'novo', a única coisa que terá são problemas. Você também precisa de uma macro gratuitamente. E você precisa de uma macro para libertar um array, uma diferenciação que não existe no C.
quinmars

8
Sem mencionar se não é você quem libera a memória, mas talvez uma biblioteca C que você esteja usando, etc. Muitos problemas possíveis sem nenhum ganho.
5153 quinmars

86
@ Hosam: Sim, definitivamente é. Se você usar, newvocê deve usar deletee se você usar, malloc()você deve free(). Nunca os misture.
Graeme Perrow

17
Se alguém seguir essa abordagem, chamar a macro NEWprovavelmente é uma má ideia, pois o recurso nunca é retornado usando delete(ou DELETE), para que você misture seu vocabulário. Em vez disso, nomeá-lo MALLOC, ou melhor CALLOC, nesse caso, faria mais sentido.
mah

139

De Wikipedia :

Vantagens para fundição

  • A inclusão do elenco pode permitir que um programa ou função C seja compilado como C ++.

  • O elenco permite versões pré-1989 do malloc que originalmente retornavam um caractere *.

  • A transmissão pode ajudar o desenvolvedor a identificar inconsistências no tamanho do tipo, caso o tipo do ponteiro de destino seja alterado, principalmente se o ponteiro for declarado longe da chamada malloc () (embora compiladores modernos e analisadores estáticos possam alertar sobre esse comportamento sem exigir a transmissão).

Desvantagens para fundição

  • Sob o padrão ANSI C, o elenco é redundante.

  • Adicionar a conversão pode mascarar a falha para incluir o cabeçalho stdlib.h, no qual o protótipo do malloc é encontrado. Na ausência de um protótipo para malloc, o padrão exige que o compilador C assuma que malloc retorne um int. Se não houver conversão, um aviso será emitido quando esse número inteiro for atribuído ao ponteiro; no entanto, com o elenco, esse aviso não é produzido, ocultando um bug. Em certas arquiteturas e modelos de dados (como LP64 em sistemas de 64 bits, em que longos e ponteiros são de 64 bits e int é de 32 bits), esse erro pode realmente resultar em um comportamento indefinido, pois o malloc declarado implicitamente retorna um valor de 32 bits. valor de bit, enquanto a função realmente definida retorna um valor de 64 bits. Dependendo das convenções de chamada e do layout da memória, isso pode resultar em esmagamento da pilha. É menos provável que esse problema passe despercebido nos compiladores modernos, como eles produzem avisos uniformemente de que uma função não declarada foi usada, um aviso ainda aparecerá. Por exemplo, o comportamento padrão do GCC é mostrar um aviso que lê "declaração implícita incompatível da função interna" independentemente de a conversão estar presente ou não.

  • Se o tipo do ponteiro for alterado em sua declaração, pode-se também precisar alterar todas as linhas nas quais o malloc é chamado e convertido.

Embora o malloc sem conversão seja o método preferido e os programadores mais experientes o escolham , você deve usar o que quiser ter conhecimento dos problemas.

ou seja: Se você precisar compilar o programa C como C ++ (embora seja uma linguagem separada), deverá converter o resultado do uso malloc.


1
O que "A transmissão pode ajudar o desenvolvedor a identificar inconsistências no tamanho do tipo deve mudar o tipo do ponteiro de destino, principalmente se o ponteiro for declarado longe da malloc()chamada " significa? Você poderia dar um exemplo?
Spikatrix

3
@CoolGuy: Veja um comentário anterior em outra resposta . Mas observe que o p = malloc(sizeof(*p) * count)idioma capta alterações no tipo automaticamente, para que você não precise receber avisos e alterar nada. Portanto, essa não é uma vantagem real versus a melhor alternativa para não transmitir.
22816 Peter Cordes

7
Esta é a resposta correta: existem prós e contras, e tudo se resume a uma questão de gosto (a menos que o código precise ser compilado como C ++ - o elenco é obrigatório).
Peter - Restabelece Monica 15/11

3
O ponto 3 é discutível, pois se o tipo do ponteiro for alterado em sua declaração, deve-se verificar todas as instâncias de malloc, realloc e resolução livre desse tipo. A seleção forçará você a fazer exatamente isso.
Michaël Roy

104

Em C, você pode converter implicitamente um voidponteiro em qualquer outro tipo de ponteiro, para que uma conversão não seja necessária. O uso de um pode sugerir ao observador casual que existe alguma razão pela qual ele é necessário, o que pode ser enganoso.


100

Você não lança o resultado do malloc, porque isso adiciona uma confusão inútil ao seu código.

A razão mais comum pela qual as pessoas lançam o resultado do malloc é porque não têm certeza sobre como a linguagem C funciona. Esse é um sinal de alerta: se você não sabe como funciona um mecanismo de linguagem específico, não adivinhe. Procure ou pergunte no Stack Overflow.

Alguns comentários:

  • Um ponteiro nulo pode ser convertido para / de qualquer outro tipo de ponteiro sem uma conversão explícita (C11 6.3.2.3 e 6.5.16.1).

  • C ++, no entanto, não permitirá uma conversão implícita entre void*e outro tipo de ponteiro. Portanto, em C ++, o elenco estaria correto. Mas se você programa em C ++, deve usar newe não malloc (). E você nunca deve compilar código C usando um compilador C ++.

    Se você precisar oferecer suporte a C e C ++ com o mesmo código-fonte, use as opções do compilador para marcar as diferenças. Não tente classificar os dois padrões de idioma com o mesmo código, porque eles não são compatíveis.

  • Se um compilador C não conseguir encontrar uma função porque você esqueceu de incluir o cabeçalho, você receberá um erro do compilador / vinculador sobre isso. Portanto, se você esqueceu de incluir <stdlib.h>isso não é nada demais, não poderá criar seu programa.

  • Em compiladores antigos que seguem uma versão do padrão com mais de 25 anos, esquecer de incluir <stdlib.h>resultaria em comportamento perigoso. Porque nesse padrão antigo, funções sem um protótipo visível convertiam implicitamente o tipo de retorno em int. A transmissão explícita do resultado do malloc ocultaria esse bug.

    Mas isso é realmente um problema. Você não está usando um computador de 25 anos, então por que você usaria um compilador de 25 anos?


9
"desordem inútil" é uma hipérbole desprezível que tende a inviabilizar qualquer possibilidade de convencer alguém que ainda não concorda com você. Um elenco certamente não é inútil; As respostas de Ron Burk e Kaz apresentam argumentos a favor do elenco com o qual eu concordo muito. Se essas preocupações pesam mais do que as mencionadas, é uma pergunta razoável a ser feita. Para mim, suas preocupações parecem relativamente menores em comparação às deles.
Don escotilha

"Um ponteiro nulo pode ser convertido para / de qualquer outro tipo de ponteiro sem uma conversão explícita" não é suportado no 6.3.2.3. Talvez você esteja pensando em "ponteiro para qualquer tipo de objeto"? "ponteiro nulo" e "ponteiro para uma função" não são tão prontamente conversíveis.
chux - Restabelece Monica

De fato, a referência estava incompleta. A parte relevante para a "implicitude" é a regra da atribuição simples 6.5.16.1. "um operando é um ponteiro para um tipo de objeto e o outro é um ponteiro para uma versão qualificada ou não qualificada do void". Eu adicionei esta referência à resposta para completude.
Lundin

91

Em C, você obtém uma conversão implícita de void *para qualquer outro ponteiro (dados).


6
@ Jens: OK, talvez o texto mais adequado seja "conversão implícita". Como o uso de variável integral na expressão de ponto flutuante.
Efraim

@ EFraim Isso realmente resultaria em um elenco, e um implícito nisso.
Mad Physicist

71

Lançando o valor retornado por malloc()Não é necessário converter agora, mas eu gostaria de acrescentar um ponto que parece que ninguém apontou:

Antigamente, ou seja, antes do ANSI C fornecer o void *tipo genérico de ponteiros, char *é o tipo para esse uso. Nesse caso, o elenco pode desligar os avisos do compilador.

Referência: C FAQ


2
Desativar avisos do compilador é uma má idéia.
Albert van der Horst

8
@AlbertvanderHorst Não, se você estiver fazendo isso, resolvendo o problema exato, o aviso existe para avisá-lo.
Dan Bechard

@Dan. Se resolver o problema exato significa reescrever uma sub-rotina para retornar tipos ANSI C modernos em vez de char *, eu concordo. Eu não chamaria isso de desligar o compilador. Não ceda aos gerentes que insistem em não haver avisos do compilador, em vez de usá-los em cada recompilação para encontrar possíveis problemas. Groetjes Albert
Albert van der Horst

53

Apenas adicionando minha experiência, estudando engenharia da computação, vejo que os dois ou três professores que eu vi escrevendo em C sempre lançam malloc, mas o que eu perguntei (com um imenso currículo e compreensão de C) me disse que é absolutamente desnecessário, mas costumava ser absolutamente específico e levar os alunos à mentalidade de serem absolutamente específicos. Essencialmente, a conversão não muda nada em como funciona, faz exatamente o que diz, aloca memória e a conversão não afeta, você obtém a mesma memória e, mesmo se a converter para outra coisa por engano (e de alguma forma evitar o compilador) erros) C acessará da mesma maneira.

Edit: Casting tem um certo ponto. Quando você usa a notação de matriz, o código gerado precisa saber quantos lugares de memória precisa avançar para alcançar o início do próximo elemento, isso é obtido através da conversão. Dessa forma, você sabe que, para um dobro, você avança 8 bytes, enquanto para um int, você avança 4, e assim por diante. Portanto, não tem efeito se você usar a notação de ponteiro; na notação de matriz, isso se torna necessário.


3
Exceto como já mencionado, o elenco pode ocultar bugs e dificultar a análise do código para o compilador ou analisador estático.
Lundin 27/05

2
"O elenco essencial não muda nada em como funciona". A conversão para o tipo correspondente não deve mudar nada, mas o tipo do var deve mudar e o elenco não corresponde mais, podem surgir problemas? IWOs, os tipos de elenco e var devem ser mantidos em sincronia - duas vezes o trabalho de manutenção.
chux - Reinstala Monica

Eu posso ver por que os profs preferem o casting. A transmissão de elenco pode ser útil do ponto de vista educacional, onde transmite as informações do instrutor e o código do aluno não precisa ser mantido - o código de descarte. No entanto, de uma perspectiva de codificação, revisão por pares e manutenção , p = malloc(sizeof *p * n);é tão simples e melhor.
chux - Restabelece Monica

53

Não é obrigatório converter os resultados de malloc, pois ele retorna void*e a void*pode ser apontado para qualquer tipo de dados.


34

Um ponteiro nulo é um ponteiro de objeto genérico e C suporta conversão implícita de um tipo de ponteiro nulo para outros tipos, portanto, não há necessidade de lançá-lo explicitamente.

No entanto, se você deseja que o mesmo código funcione perfeitamente compatível em uma plataforma C ++, que não suporta conversão implícita, é necessário fazer a conversão de tipo, para que tudo dependa da usabilidade.


2
Não é um caso de uso normal compilar uma única fonte como C e C ++ (em vez de, por exemplo, usar um arquivo de cabeçalho contendo declarações para vincular o código C e C ++). Usar malloce amigos em C ++ é um bom sinal de aviso de que merece atenção especial (ou reescrita em C).
Toby Speight

1
"Um ponteiro vazio é um ponteiro genérico" -> "Um ponteiro vazio é um ponteiro genérico de objeto ". O tamanho dos ponteiros de função pode exceder void *, portanto, void *é insuficiente para armazenar bem um ponteiro de função.
chux - Reinstala Monica

minha intenção dessa linha era a mesma, mas mesmo assim obrigado @chux pela sugestão.
Endeavour

33

Isto é o que o manual de referência da biblioteca C GNU diz:

Você pode armazenar o resultado mallocem qualquer variável de ponteiro sem conversão, porque o ISO C converte automaticamente o tipo void *em outro tipo de ponteiro quando necessário. Mas a conversão é necessária em contextos diferentes dos operadores de atribuição ou se você quiser que seu código seja executado no C. tradicional

E, de fato, o padrão ISO C11 (p347) diz o seguinte:

O ponteiro retornado se a alocação for bem-sucedida está adequadamente alinhado para que possa ser atribuído a um ponteiro para qualquer tipo de objeto com um requisito de alinhamento fundamental e, em seguida, usado para acessar esse objeto ou uma matriz de tais objetos no espaço alocado (até o espaço é explicitamente desalocado)


31

O tipo retornado é nulo *, que pode ser convertido no tipo de ponteiro de dados desejado para ser desreferenciável.


1
void* pode ser convertido para o tipo desejado, mas não é necessário fazê-lo, pois ele será convertido automaticamente. Portanto, o elenco não é necessário e, de fato, indesejável pelas razões mencionadas nas respostas de maior pontuação.
Toby Speight

mas somente se você precisar desreferenciá-la "on the fly", se criar uma variável, ela será convertida com segurança e automaticamente no tipo efetivo da variável, sem conversão (em C).
Ferrarezi

28

No idioma C, um ponteiro nulo pode ser atribuído a qualquer ponteiro, e é por isso que você não deve usar uma conversão de tipo. Se você deseja alocação "tipo seguro", posso recomendar as seguintes funções de macro, que eu sempre uso nos meus projetos em C:

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

Com estes no lugar, você pode simplesmente dizer

NEW_ARRAY(sieve, length);

Para matrizes não dinâmicas, a terceira macro de função obrigatória é

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

o que torna os loops de matriz mais seguros e mais convenientes:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

"um ponteiro nulo pode ser atribuído a qualquer ponteiro de objeto " Os ponteiros de função são outro problema, embora não malloc()um.
chux - Restabelece Monica

A atribuição de um void*para / de um ponteiro de função pode perder informações, de modo que "um ponteiro nulo pode ser atribuído a qualquer ponteiro" é um problema nesses casos. Atribuir um void*, de malloc() para qualquer ponteiro de objeto não é um problema.
chux - Restabelece Monica

O docomentário do loop está relacionado às macros que envolvem um loop e ainda estão se perguntando sobre a questão do título. Removendo esse comentário. Também derrubará este aqui mais tarde.
chux - Restabelece Monica

27

Depende da linguagem de programação e do compilador. Se você usarmalloc C, não há necessidade de digitar a conversão, pois ela digitará automaticamente a conversão. No entanto, se você estiver usando C ++, digite cast porque mallocretornará um void*tipo.


1
A função malloc retorna um ponteiro nulo em C, mas as regras da linguagem são diferentes de C ++.
August Karlstrom 5/09/2015

16

Pessoas acostumadas a GCC e Clang são mimadas. Não é tão bom assim por aí.

Fiquei bastante horrorizado ao longo dos anos pelos compiladores incrivelmente envelhecidos que fui obrigado a usar. Freqüentemente, empresas e gerentes adotam uma abordagem ultra-conservadora para alterar os compiladores e nem sequer testam se um novo compilador (com melhor conformidade com os padrões e otimização de código) funcionará em seu sistema. A realidade prática para os desenvolvedores que trabalham é que, quando você está codificando, precisa cobrir suas bases e, infelizmente, lançar mallocs é um bom hábito, se você não pode controlar qual compilador pode ser aplicado ao seu código.

Sugiro também que muitas organizações apliquem seus próprios padrões de codificação e que deve ser o método que as pessoas seguem se for definido. Na ausência de orientação explícita, tenho a tendência de compilar em todos os lugares, em vez da adesão servil a um padrão.

O argumento de que não é necessário sob os padrões atuais é bastante válido. Mas esse argumento omite os aspectos práticos do mundo real. Não codificamos em um mundo regido exclusivamente pelo padrão do dia, mas pelas práticas do que eu gosto de chamar de "campo da realidade da administração local". E isso é torcido e distorcido mais do que o tempo espacial jamais esteve. :-)

YMMV.

Costumo pensar em lançar malloc como uma operação defensiva. Não é bonito, não é perfeito, mas geralmente seguro. (Honestamente, se você não incluiu stdlib.h, você tem muito mais problemas do que lançar malloc!).


15

Eu coloquei no elenco simplesmente para mostrar a desaprovação do buraco feio no sistema de tipos, que permite que códigos como o seguinte trecho sejam compilados sem diagnóstico, mesmo que nenhum elenco seja usado para provocar uma conversão ruim:

double d;
void *p = &d;
int *q = p;

Eu gostaria que isso não existisse (e não existe em C ++) e, por isso, fiz a transmissão. Representa meu gosto e minha política de programação. Não estou apenas lançando um ponteiro, mas efetivamente, votando e expulsando demônios da estupidez . Se eu realmente não posso expulsar a estupidez , pelo menos deixe-me expressar o desejo de fazê-lo com um gesto de protesto.

De fato, uma boa prática é agrupar malloc(e amigos) com funções que retornam unsigned char *e basicamente nunca usar void *em seu código. Se você precisar de um ponteiro genérico para qualquer objeto, use a char *ou unsigned char *e tenha projeções nas duas direções. O único relaxamento que pode ser tolerado, talvez, é usar funções como memsete memcpysem elencos.

No tópico fundição e compatibilidade com C ++, se você escrever seu código para que ele seja compilado tanto em C quanto em C ++ (nesse caso, você precisará converter o valor de retorno mallocao atribuí-lo a algo diferente de void *), poderá fazer uma ajuda muito útil coisa para você: você pode usar macros para conversão que convertem para conversões no estilo C ++ ao compilar como C ++, mas reduzem para uma conversão C ao compilar como C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

Se você aderir a essas macros, uma simples greppesquisa na sua base de códigos por esses identificadores mostrará onde estão todas as suas transmissões, para que você possa verificar se alguma delas está incorreta.

A partir de agora, se você compilar regularmente o código com C ++, ele aplicará o uso de uma conversão apropriada. Por exemplo, se você usar strip_qualapenas para remover um constou volatile, mas o programa mudar de tal maneira que uma conversão de tipo esteja envolvida, você receberá um diagnóstico e precisará usar uma combinação de conversões para obter a conversão desejada.

Para ajudá-lo a aderir a essas macros, o compilador GNU C ++ (não C!) Possui um recurso bonito: um diagnóstico opcional produzido para todas as ocorrências de conversões no estilo C.

     -Construção no estiloold (apenas C ++ e Objective-C ++)
         Avisar se uma conversão de estilo antigo (estilo C) para um tipo não nulo é usada
         dentro de um programa C ++. O novo estilo lança (dynamic_cast,
         static_cast, reinterpret_cast e const_cast) são menos vulneráveis
         para efeitos não intencionais e muito mais fáceis de pesquisar.

Se o seu código C for compilado como C ++, você poderá usar esta -Wold-style-castopção para descobrir todas as ocorrências da (type)sintaxe de conversão que podem surgir no código e acompanhar esses diagnósticos, substituindo-o por uma escolha apropriada dentre as macros acima (ou um combinação, se necessário).

Esse tratamento de conversões é a maior justificativa técnica autônoma para trabalhar em um "C limpo": o dialeto C e C ++ combinado, que por sua vez tecnicamente justifica lançar o valor de retorno de malloc.


Como outro apontado, eu recomendaria geralmente não misturar código C e C ++. No entanto, se você tiver um bom motivo para fazê-lo, as macros podem ser úteis.
Phil1970

@ Phil1970 Tudo está escrito em um dialeto coeso, que é portátil para compiladores C e C ++, e tira proveito de alguns recursos do C ++. Deve ser tudo compilado como C ++, ou então tudo compilado como C.
Kaz

Ou seja, o que eu estava tentando dizer no comentário anterior é que não há mistura de C e C ++. A intenção é que o código seja todo compilado como C ou todo compilado como C ++.
Kaz

15

A melhor coisa a fazer ao programar em C sempre que possível:

  1. Faça seu programa compilar através de um compilador C com todos os avisos ativados -Walle corrija todos os erros e avisos
  2. Verifique se não há variáveis ​​declaradas como auto
  3. Em seguida, compile-o usando um compilador C ++ com -Walle -std=c++11. Corrija todos os erros e avisos.
  4. Agora compile usando o compilador C novamente. Seu programa agora deve ser compilado sem nenhum aviso e conter menos erros.

Este procedimento permite tirar proveito da verificação estrita do tipo C ++, reduzindo assim o número de bugs. Em particular, este procedimento obriga a incluir stdlib.hou você receberá

malloc não foi declarado nesse escopo

e também obriga a lançar o resultado mallocou você receberá

conversão inválida de void*paraT*

ou qualquer que seja o seu tipo de alvo.

Os únicos benefícios de escrever em C em vez de C ++ que posso encontrar são

  1. C tem uma ABI bem especificada
  2. C ++ pode gerar mais código [exceções, RTTI, modelos, polimorfismo de tempo de execução ]

Observe que os segundos contras devem, no caso ideal, desaparecer ao usar o subconjunto comum a C junto com o recurso polimórfico estático .

Para aqueles que consideram as regras estritas do C ++ inconvenientes, podemos usar o recurso C ++ 11 com tipo inferido

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

18
Use um compilador C para código C. Use um compilador C ++ para código C ++. Sem ifs, buts. Reescrever seu código C em C ++ é outra coisa completamente diferente e pode - ou pode não valer a pena - o tempo e os riscos.
Toby Speight

2
Gostaria de adicionar ao conselho do @TobySpeight: se você precisar usar o código C em um projeto C ++, geralmente poderá compilar o código C como C (por exemplo gcc -c c_code.c), o código C ++ como C ++ (por exemplo g++ -c cpp_code.cpp) e vinculá-los (por exemplo, gcc c_code.o cpp_code.oou vice-versa, dependendo das dependências do projeto). Agora deve haver nenhuma razão para privar-se de quaisquer características agradáveis de uma ou outra língua ...
autista

1
@ user877329 É uma alternativa mais sensata à adição meticulosa de elencos ao código que reduz a legibilidade do código, apenas por ser "compatível com C ++".
autistic

1
Provavelmente, a principal vantagem nesse contexto é que C permite escrever p = malloc(sizeof(*p));, o que não precisa ser alterado em primeiro lugar se pmudar para um nome de tipo diferente. A "vantagem" proposta da conversão é que você recebe um erro de compilação se pfor do tipo errado, mas é ainda melhor se apenas funcionar.
22816 Peter Cordes

1
Eu gostaria de mencionar que escrever em C pode ser necessário ao direcionar plataformas sem os compiladores C ++ adequados. Exceções e modelos são recursos que normalmente ajudam o c ++ a gerar código menor e / ou mais eficiente, enquanto o polimorfismo de tempo de execução no C ++ é equivalente a C.
user7860670

15

Não, você não lança o resultado de malloc().

Em geral, você não transmite de ou paravoid * .

Um motivo típico para não fazer isso é que a falha em #include <stdlib.h>passar despercebida. Isso não é mais um problema há muito tempo, pois o C99 tornou ilegal a declaração implícita de funções . Portanto, se o seu compilador estiver em conformidade com pelo menos o C99, você receberá uma mensagem de diagnóstico.

Mas há uma razão muito mais forte para não introduzir projeções desnecessárias de ponteiros:

Em C, uma conversão de ponteiro é quase sempre um erro . Isso ocorre devido à seguinte regra ( §6.5 p7 no N1570, o último rascunho do C11):

Um objeto deve ter seu valor armazenado acessado apenas por uma expressão lvalue que possui um dos seguintes tipos:
- um tipo compatível com o tipo efetivo do objeto,
- uma versão qualificada de um tipo compatível com o tipo efetivo do objeto,
- um tipo que é o tipo assinado ou não assinado correspondente ao tipo efetivo do objeto,
- um tipo que é o tipo assinado ou não assinado, correspondente a uma versão qualificada do tipo efetivo do objeto,
- um tipo agregado ou de união que inclui um dos tipos mencionados acima entre seus membros (incluindo, recursivamente, um membro de uma união subagregada ou contida), ou
- um tipo de caractere.

Isso também é conhecido como regra estrita de alias . Portanto, o código a seguir é um comportamento indefinido :

long x = 5;
double *p = (double *)&x;
double y = *p;

E, às vezes surpreendentemente, também é o seguinte:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

Às vezes, você não precisa ponteiros do elenco, mas dada a regra aliasing estrita , você tem que ter muito cuidado com ele. Portanto, qualquer ocorrência de um ponteiro convertido em seu código é um local em que você deve verificar novamente sua validade . Portanto, você nunca escreve um elenco desnecessário de ponteiros.

tl; dr

Em poucas palavras: como em C, qualquer ocorrência de vazamento de ponteiro deve exibir uma bandeira vermelha para código que requer atenção especial, você nunca deve escrever vazamentos desnecessários de ponteiro.


Notas laterais:

  • Há casos em que você realmente precisa de uma conversão void *, por exemplo, se deseja imprimir um ponteiro:

    int x = 5;
    printf("%p\n", (void *)&x);

    O elenco é necessário aqui, porque printf()é uma função variável, portanto, as conversões implícitas não funcionam.

  • Em C ++, a situação é diferente. A conversão de tipos de ponteiro é algo comum (e correto) ao lidar com objetos de classes derivadas. Portanto, faz sentido que, em C ++, a conversão de e para nãovoid * esteja implícita. O C ++ possui um conjunto completo de diferentes sabores de conversão.


1
Nos seus exemplos, você evita nulos *. existe uma diferença entre converter de double * para int * e vice-versa. malloc retorna pointel alinhado ao maior tipo padrão, para que não haja regras de aliasing quebradas, mesmo se alguém lança esse ponteiro alinhado para outro tipo.
P__J__

O aliasing não tem nada a ver com o alinhamento e com o restante do seu comentário - você obviamente não entendeu o argumento.

@ PeterJ: por precaução, o objetivo é evitar um lançamento desnecessário de ponteiros, para que não pareça um pedaço de código ao qual você deve prestar atenção especial.

A questão estrita do alias não tem nada a ver com ponteiros nulos. Para obter erros causados ​​por violações estritas de alias, você deve des-referenciar os dados apontados. E como você não pode desassociar um ponteiro nulo, esses bugs, por definição, não estão relacionados ao ponteiro nulo, mas a outra coisa.
Lundin

Em vez disso, você teria que fazer uma regra para banir todos os lançamentos de ponteiros. Mas como você escreveria rotinas de serialização e programação relacionada a hardware? Coisas que são a força de C. Tais elencos são bons se você souber o que está fazendo.
Lundin

15

Eu prefiro fazer o elenco, mas não manualmente. O meu favorito é usar g_newe g_new0macros de glib. Se glib não for usado, eu adicionaria macros semelhantes. Essas macros reduzem a duplicação de código sem comprometer a segurança do tipo. Se você errar o tipo, obteria uma conversão implícita entre ponteiros não nulos, o que causaria um aviso (erro em C ++). Se você esquecer de incluir o cabeçalho que define g_newe g_new0, você receberá um erro. g_newe g_new0ambos têm os mesmos argumentos, ao contrário de mallocque leva menos argumentos que calloc. Basta adicionar 0para obter memória inicializada com zero. O código pode ser compilado com um compilador C ++ sem alterações.


12

A conversão é apenas para C ++ e não C. No caso de você estar usando um compilador C ++, é melhor alterá-lo para o compilador C.


9

O conceito por trás do ponteiro nulo é que ele pode ser convertido para qualquer tipo de dados, por isso o malloc retorna nulo. Além disso, você deve estar ciente da conversão automática de tipos. Portanto, não é obrigatório lançar o ponteiro, embora você deva fazê-lo. Ajuda a manter o código limpo e ajuda na depuração


11
" Não é obrigatório - embora você deva fazê-lo " - acho que há uma contradição por lá!
Toby Speight

5
Eu acho que você deve ler este post para alguém e ver se ele entende o que você está tentando dizer. Em seguida, reescreva-o, deixando claro o que você quer dizer. Realmente não consigo entender qual é a sua resposta.
precisa saber é o seguinte

9

Um ponteiro nulo é um ponteiro genérico e C suporta conversão implícita de um tipo de ponteiro nulo para outros tipos, portanto, não há necessidade de lançá-lo explicitamente.

No entanto, se você deseja que o mesmo código funcione perfeitamente compatível em uma plataforma C ++, que não suporta conversão implícita, é necessário fazer a conversão de tipo, para que tudo dependa da usabilidade.


9
  1. Como já foi dito, não é necessário para C, mas para C ++.

  2. A inclusão do elenco pode permitir que um programa ou função C seja compilado como C ++.

  3. Em C, isso é desnecessário, pois o void * é promovido de forma automática e segura para qualquer outro tipo de ponteiro.

  4. Mas se você converter, poderá ocultar um erro se você esquecer de incluir stdlib.h . Isso pode causar falhas (ou, pior, não causar uma falha até muito mais tarde em alguma parte totalmente diferente do código).

    Como stdlib.h contém o protótipo para malloc, foi encontrado. Na ausência de um protótipo para malloc, o padrão exige que o compilador C assuma malloc retornando um int. Se não houver conversão, um aviso será emitido quando esse número inteiro for atribuído ao ponteiro; no entanto, com o elenco, esse aviso não é produzido, ocultando um bug.


7

A conversão do malloc é desnecessária em C, mas obrigatória em C ++.

A transmissão é desnecessária em C devido a:

  • void * é promovido de forma automática e segura para qualquer outro tipo de ponteiro no caso de C.
  • Pode ocultar um erro se você se esquecer de incluir <stdlib.h>. Isso pode causar falhas.
  • Se ponteiros e números inteiros tiverem tamanhos diferentes, você estará ocultando um aviso ao transmitir e poderá perder bits do seu endereço retornado.
  • Se o tipo do ponteiro for alterado em sua declaração, talvez seja necessário alterar todas as linhas em que mallocé chamado e convertido.

Por outro lado, a transmissão pode aumentar a portabilidade do seu programa. isto é, permite que um programa ou função C compile como C ++.


0

Para mim, a conclusão e conclusão aqui é que a transmissão mallocem C NÃO é totalmente necessária, mas, se você transmitir, isso não afetará, mallocpois mallocainda alocará o espaço de memória abençoado solicitado. Outra coisa que leva para casa é a razão ou uma das razões pelas quais as pessoas fazem a transmissão e isso lhes permite compilar o mesmo programa em C ou C ++.

Pode haver outras razões, mas outras, quase certamente, o colocariam em sérios problemas mais cedo ou mais tarde.


0

Você pode, mas não precisa converter em C. Você deve converter se esse código for compilado como C ++.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.